Skip to content

Commit e955d3b

Browse files
committed
make ItemRepository, ProfileItemRepo, DogItemRepo, CatItemRepo
1 parent 321cd58 commit e955d3b

File tree

14 files changed

+417
-51
lines changed

14 files changed

+417
-51
lines changed

app/src/test/java/com/willowtreeapps/namegame/ReducersTest.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.willowtreeapps.namegame
22

33
import com.willowtreeapps.common.*
4-
import com.willowtreeapps.common.boundary.displayName
54
import com.willowtreeapps.common.repo.MockRepositoryFactory
5+
import com.willowtreeapps.common.repo.ProfileItemRepository
6+
import kotlinx.coroutines.runBlocking
67
import org.junit.Assert.*
78
import org.junit.Test
89
import java.lang.Exception
@@ -32,8 +33,9 @@ class ReducersTest {
3233

3334
@Test
3435
fun `generate N distinct random rounds`() {
35-
val profiles = MockRepositoryFactory.getValidResponse()
36-
val rounds = generateRounds(profiles, 10)
36+
37+
val profiles = runBlocking { ProfileItemRepository(MockRepositoryFactory().success()).fetchItems()}.response
38+
val rounds = generateRounds(profiles!!, 10)
3739

3840
assertEquals(10, rounds.size)
3941
assertEquals(10, rounds.distinctBy { it.profileId }.size)
@@ -49,7 +51,7 @@ class ReducersTest {
4951
@Test
5052
fun `isLoadingProfiles set false on success`() {
5153
val initial = generateInitialTestState().copy(isLoadingProfiles = true)
52-
val final = reducer(initial, Actions.FetchingProfilesSuccessAction(MockRepositoryFactory.getValidResponse()))
54+
val final = reducer(initial, Actions.FetchingProfilesSuccessAction(runBlocking { ProfileItemRepository(MockRepositoryFactory().success()).fetchItems()}.response!!))
5355

5456
assertFalse(final.isLoadingProfiles)
5557
}
@@ -81,7 +83,7 @@ class ReducersTest {
8183
@Test
8284
fun `mark current round as CORRECT when name matches`() {
8385
val initial = generateInitialTestState()
84-
val answer = initial.currentQuestionProfile().displayName()
86+
val answer = initial.currentQuestionItem().displayName()
8587

8688
val final = reducer(initial, Actions.NamePickedAction(answer))
8789

@@ -136,7 +138,7 @@ class ReducersTest {
136138
}
137139

138140
private fun generateInitialTestState(): AppState {
139-
val initialState = reducer(AppState(), Actions.FetchingProfilesSuccessAction(MockRepositoryFactory.getValidResponse()))
141+
val initialState = reducer(AppState(), Actions.FetchingProfilesSuccessAction(runBlocking { ProfileItemRepository(MockRepositoryFactory().success()).fetchItems()}.response!!))
140142
return initialState
141143
}
142144
}

app/src/test/java/com/willowtreeapps/namegame/ExampleUnitTest.kt renamed to app/src/test/java/com/willowtreeapps/namegame/RepositoriesTest.kt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,35 @@ import org.junit.Test
1515
*
1616
* See [testing documentation](http://d.android.com/tools/testing).
1717
*/
18-
class ExampleUnitTest {
18+
class RepositoriesTest {
1919
private val repo = KtorProfilesRepository()
2020

2121
@Test
2222
fun fetchProfiles() {
23-
GlobalScope.launch {
24-
async {
25-
val result = repo.profiles()
26-
if (result.isSuccessful) {
27-
assertNotNull(result.response)
28-
assertTrue(result.response?.isNotEmpty() ?: false)
29-
} else {
30-
fail("Failure response from profiles repo: ${result.message}")
31-
}
32-
}.await()
23+
val result = runBlocking { repo.profiles() }
24+
if (result.isSuccessful) {
25+
assertNotNull(result.response)
26+
assertTrue(result.response?.isNotEmpty() ?: false)
27+
} else {
28+
fail("Failure response from cats repo: ${result.message}")
3329
}
34-
Thread.sleep(5000)
3530
}
3631

3732
@Test
3833
fun deserializeProfilesResponse() {
3934
val response = Json.nonstrict.parse(ProfileListHolderSerializer(), MockRepositoryFactory.VALID_RESPONSE_JSON)
4035
assertNotNull(response.profiles)
4136
}
37+
38+
@Test
39+
fun fetchDogs() {
40+
val result = runBlocking { repo.profiles() }
41+
if (result.isSuccessful) {
42+
assertNotNull(result.response)
43+
assertTrue(result.response?.isNotEmpty() ?: false)
44+
} else {
45+
fail("Failure response from cats repo: ${result.message}")
46+
}
47+
}
4248
}
4349

common/src/androidMain/kotlin/com/willowtreeapps/common/PlatformDispatcher.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ import kotlin.coroutines.CoroutineContext
77

88
actual object PlatformDispatcher : CoroutineDispatcher() {
99
override fun dispatch(context: CoroutineContext, block: Runnable) {
10-
Dispatchers.Main.dispatch(context, block)
10+
Dispatchers.IO.dispatch(context, block)
1111
}
1212
}

common/src/commonMain/kotlin/com/willowtreeapps/common/Actions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.willowtreeapps.common.repo.Profile
66
sealed class Actions : Action {
77

88
class FetchingProfilesStartedAction
9-
class FetchingProfilesSuccessAction(val profiles: List<Profile>)
9+
class FetchingProfilesSuccessAction(val profiles: List<Item>)
1010
class FetchingProfilesFailedAction(val message: String)
1111

1212
class NamePickedAction(val name: String)

common/src/commonMain/kotlin/com/willowtreeapps/common/AppState.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.willowtreeapps.common
22

3+
import com.willowtreeapps.common.boundary.displayName
34
import com.willowtreeapps.common.repo.Profile
45

56
data class AppState(val isLoadingProfiles: Boolean = false,
6-
val profiles: List<Profile> = listOf(),
7+
val items: List<Item> = listOf(),
78
val errorLoadingProfiles: Boolean = false,
89
val errorMsg: String = "",
910
val currentQuestionIndex: Int = 0,
@@ -16,8 +17,6 @@ data class AppState(val isLoadingProfiles: Boolean = false,
1617
val INITIAL_STATE = AppState()
1718
}
1819

19-
fun Question.profile(): Profile = profiles.find { ProfileId(it.id) == this.profileId }!!
20-
2120
val timerText: String
2221
get() = if (questionClock < 0) "" else if (questionClock >= 0) questionClock.toString() else "TIME'S UP!!"
2322

@@ -27,9 +26,9 @@ data class AppState(val isLoadingProfiles: Boolean = false,
2726
else
2827
null
2928

30-
fun getProfile(id: ProfileId?) = profiles.find { it.id == id?.id }
29+
fun getItem(id: ProfileId?) = items.find { it.id == id }
3130

32-
fun currentQuestionProfile() = getProfile(currentQuestion?.profileId)!!
31+
fun currentQuestionItem() = getItem(currentQuestion?.profileId)!!
3332

3433
fun isGameComplete(): Boolean = currentQuestionIndex >= questions.size || (currentQuestionIndex == questions.size - 1 && questions[currentQuestionIndex].status != Question.Status.UNANSWERED)
3534

@@ -51,6 +50,19 @@ data class Question(val profileId: ProfileId,
5150
}
5251
}
5352

53+
data class Item(val id: ProfileId,
54+
val imageUrl: String,
55+
val firstName: String,
56+
val lastName: String) {
57+
58+
fun displayName() = "$firstName $lastName"
59+
60+
fun matches(name: String): Boolean {
61+
return displayName() == name
62+
}
63+
64+
}
65+
5466
data class UserSettings(val numQuestions: Int) {
5567
companion object {
5668
fun defaults() = UserSettings(3)

common/src/commonMain/kotlin/com/willowtreeapps/common/NetworkThunks.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.willowtreeapps.common
22

33
import com.beyondeye.reduks.*
4-
import com.willowtreeapps.common.repo.KtorProfilesRepository
4+
import com.willowtreeapps.common.repo.*
55
import kotlinx.coroutines.*
66
import kotlin.coroutines.CoroutineContext
77

@@ -10,19 +10,18 @@ import kotlin.coroutines.CoroutineContext
1010
* actions. This allows dispatching a loading, success, and failure state.
1111
*/
1212
class NetworkThunks(private val networkContext: CoroutineContext,
13-
val store: Store<AppState>,
14-
private val timerThunks: TimerThunks) : CoroutineScope {
13+
val store: Store<AppState>) : CoroutineScope {
1514
private val job = Job()
1615
override val coroutineContext: CoroutineContext
1716
get() = networkContext + job
1817

19-
private val repo = KtorProfilesRepository()
18+
private val repo = CatItemRepository()
2019

2120
fun fetchProfiles(): ThunkImpl<AppState> = ThunkFn { dispatcher, state ->
2221
Logger.d("Fetching StoreInfo and Feed")
2322
launch {
2423
store.dispatch(Actions.FetchingProfilesStartedAction())
25-
val result = repo.profiles()
24+
val result = repo.fetchItems()
2625
if (result.isSuccessful) {
2726
Logger.d("Success")
2827
store.dispatch(Actions.FetchingProfilesSuccessAction(result.response!!))

common/src/commonMain/kotlin/com/willowtreeapps/common/Reducers.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.willowtreeapps.common
22

33
import com.willowtreeapps.common.Actions.*
4-
import com.willowtreeapps.common.repo.Profile
54
import com.willowtreeapps.common.util.TimeUtil
65
import kotlin.math.abs
76
import kotlin.random.Random
@@ -15,11 +14,11 @@ fun reducer(state: AppState, action: Any): AppState =
1514
is FetchingProfilesStartedAction -> state.copy(isLoadingProfiles = true)
1615
is FetchingProfilesSuccessAction -> {
1716
val rounds = generateRounds(action.profiles, state.settings.numQuestions)
18-
state.copy(isLoadingProfiles = false, profiles = action.profiles, questions = rounds)
17+
state.copy(isLoadingProfiles = false, items = action.profiles, questions = rounds)
1918
}
2019
is FetchingProfilesFailedAction -> state.copy(isLoadingProfiles = false, errorLoadingProfiles = true, errorMsg = action.message)
2120
is NamePickedAction -> {
22-
val status = if (state.currentQuestionProfile().matches(action.name)) {
21+
val status = if (state.currentQuestionItem().matches(action.name)) {
2322
Question.Status.CORRECT
2423
} else {
2524
Question.Status.INCORRECT
@@ -45,14 +44,14 @@ fun reducer(state: AppState, action: Any): AppState =
4544
else -> throw AssertionError("Action ${action::class.simpleName} not handled")
4645
}
4746

48-
fun generateRounds(profiles: List<Profile>, n: Int): List<Question> =
47+
fun generateRounds(profiles: List<Item>, n: Int): List<Question> =
4948
profiles.takeRandomDistinct(n)
5049
.map {
5150
val choiceList = profiles.takeRandomDistinct(3).toMutableList()
5251
choiceList.add(abs(random.nextInt() % 4), it)
5352

54-
Question(profileId = ProfileId(it.id), choices = choiceList
55-
.map { ProfileId(it.id) })
53+
Question(profileId = it.id, choices = choiceList
54+
.map { it.id })
5655
}
5756

5857

common/src/commonMain/kotlin/com/willowtreeapps/common/boundary/TransformFunctions.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import com.willowtreeapps.common.repo.Profile
77
* Functions for transforming AppState data into ViewState data to be used by Views.
88
*/
99
fun AppState.toQuestionViewState(): QuestionViewState {
10-
val profile = currentQuestionProfile()
11-
val imageUrl = profile.headshot.url
12-
val choice1 = getProfile(currentQuestion?.choices?.get(0))!!.displayName()
13-
val choice2 = getProfile(currentQuestion?.choices?.get(1))!!.displayName()
14-
val choice3 = getProfile(currentQuestion?.choices?.get(2))!!.displayName()
15-
val choice4 = getProfile(currentQuestion?.choices?.get(3))!!.displayName()
16-
val correctBtnNum = currentQuestion?.choices?.indexOfFirst { it.id == profile.id }!! + 1
17-
var selectedBtnNum = currentQuestion?.choices?.indexOfFirst { getProfile(it)?.matches(currentQuestion?.answerName ?: "") ?: false}
10+
val profile = currentQuestionItem()
11+
val imageUrl = profile.imageUrl
12+
val choice1 = getItem(currentQuestion?.choices?.get(0))!!.displayName()
13+
val choice2 = getItem(currentQuestion?.choices?.get(1))!!.displayName()
14+
val choice3 = getItem(currentQuestion?.choices?.get(2))!!.displayName()
15+
val choice4 = getItem(currentQuestion?.choices?.get(3))!!.displayName()
16+
val correctBtnNum = currentQuestion?.choices?.indexOfFirst { it == profile.id }!! + 1
17+
var selectedBtnNum = currentQuestion?.choices?.indexOfFirst { getItem(it)?.matches(currentQuestion?.answerName ?: "") ?: false}
1818
if (selectedBtnNum != null) {
1919
selectedBtnNum += 1
2020
}
2121
return QuestionViewState(title = "Who is this?",
22-
profileImageUrl = "https:$imageUrl",
22+
profileImageUrl = imageUrl,
2323
currentQuestion = (currentQuestionIndex + 1).toString(),
2424
numQuestions = questions.size.toString(),
2525
button1Text = choice1,

common/src/commonMain/kotlin/com/willowtreeapps/common/repo/GatewayResponse.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.willowtreeapps.common.repo
22

3+
import kotlinx.coroutines.delay
4+
35

46
/**
57
* Wrapper around Gateway responses that allows returning an error state with code and message.
@@ -60,12 +62,12 @@ data class GenericError(val message: String)
6062
* @param ex exception that will be thrown if a success full response is not received
6163
* @return GatewayResponse will only return GatewayResponse object if it is successful
6264
*/
63-
fun retrySuccessOrThrow(numRetries: Int, retryWaitInMs: Long, ex: Exception, f: () -> GatewayResponse<*, *>): GatewayResponse<*, *> {
65+
suspend fun <R, E> retrySuccessOrThrow(numRetries: Int, retryWaitInMs: Long, ex: Exception, f: suspend () -> GatewayResponse<R, E>): GatewayResponse<R, E> {
6466
val response = f()
6567
return if (response.isSuccessful) {
6668
response
6769
} else if (response.isFailure && numRetries > 0) {
68-
// Thread.sleep(retryWaitInMs)
70+
delay(retryWaitInMs)
6971
retrySuccessOrThrow(numRetries - 1, retryWaitInMs * 2, ex, f)
7072
} else {
7173
throw ex
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.willowtreeapps.common.repo
2+
3+
import com.willowtreeapps.common.Item
4+
import com.willowtreeapps.common.PlatformDispatcher
5+
import com.willowtreeapps.common.ProfileId
6+
7+
/**
8+
* interface for providing list of items for the game. Items are generic representation.
9+
* This may be used for any data source to generate questions.
10+
*/
11+
interface ItemRepository {
12+
suspend fun fetchItems(): GatewayResponse<List<Item>, GenericError>
13+
}
14+
15+
class ProfileItemRepository(val repo: ProfilesRepository = KtorProfilesRepository()) : ItemRepository {
16+
override suspend fun fetchItems(): GatewayResponse<List<Item>, GenericError> {
17+
val results = repo.profiles()
18+
return if (results.isSuccessful) {
19+
GatewayResponse.createSuccess(results.response?.map { Item(id = ProfileId(it.id), firstName = it.firstName, lastName = it.lastName, imageUrl = "https:${it.headshot.url}") },
20+
200, "")
21+
} else {
22+
GatewayResponse.createError(GenericError("Error"), 500, "")
23+
}
24+
}
25+
26+
}
27+
28+
class DogItemRepository(val repo: KtorDogsRepository = KtorDogsRepository(PlatformDispatcher)) : ItemRepository {
29+
override suspend fun fetchItems(): GatewayResponse<List<Item>, GenericError> {
30+
val results = repo.dogs()
31+
return if (results.isSuccessful) {
32+
GatewayResponse.createSuccess(results.response?.map {
33+
Item(id = ProfileId(it.breed + "_" + it.subBreed),
34+
firstName = it.breed, lastName = it.subBreed ?: "",
35+
imageUrl = it.imageUrl)
36+
},
37+
200, "")
38+
} else {
39+
GatewayResponse.createError(GenericError("Error"), 500, "")
40+
}
41+
}
42+
43+
}
44+
45+
class CatItemRepository(val repo: KtorCatsRepository = KtorCatsRepository(PlatformDispatcher)) : ItemRepository {
46+
override suspend fun fetchItems(): GatewayResponse<List<Item>, GenericError> {
47+
val results = repo.allBreeds()
48+
return if (results.isSuccessful) {
49+
GatewayResponse.createSuccess(results.response?.map {
50+
Item(id = ProfileId(it.id),
51+
firstName = it.breed, lastName = "",
52+
imageUrl = it.imageUrl)
53+
},
54+
200, "")
55+
} else {
56+
GatewayResponse.createError(GenericError("Error"), 500, "")
57+
}
58+
}
59+
60+
}
61+

0 commit comments

Comments
 (0)