Skip to content

Commit

Permalink
Refactor around search
Browse files Browse the repository at this point in the history
  • Loading branch information
takahirom committed Dec 29, 2017
1 parent e3581ea commit 65949f1
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.rxkotlin.Flowables
import io.reactivex.rxkotlin.Singles
import timber.log.Timber
import javax.inject.Inject

Expand Down Expand Up @@ -47,8 +48,8 @@ class SessionDataRepository @Inject constructor(
override val speakers: Flowable<List<Speaker>> =
sessionDatabase.getAllSpeaker()
.map { speakers ->
speakers.map { speaker -> speaker.toSpeaker() }
}
speakers.map { speaker -> speaker.toSpeaker() }
}

override val roomSessions: Flowable<Map<Room, List<Session>>>
= sessions.map { sessionList -> sessionList.groupBy { it.room } }
Expand All @@ -70,6 +71,15 @@ class SessionDataRepository @Inject constructor(
.toCompletable()
}

override fun search(query: String): Single<SearchResult> = Singles.zip(
sessions.map {
it.filter { it.title.contains(query) || it.desc.contains(query) }
}.firstOrError(),
speakers.map {
it.filter { it.name.contains(query) }
}.firstOrError(),
{ sessions: List<Session>, speakers: List<Speaker> -> SearchResult(sessions, speakers) })

companion object {
const val DEBUG = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ interface SessionRepository {

@CheckResult fun refreshSessions(): Completable
@CheckResult fun favorite(session: Session): Single<Boolean>
@CheckResult fun search(query: String): Single<SearchResult>

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,4 @@ fun <T> Observable<T>.toResult(schedulerProvider: SchedulerProvider): Observable
}

fun <T> Single<T>.toResult(schedulerProvider: SchedulerProvider): Observable<Result<T>> =
compose { item ->
item
.map { Result.success(it) }
.onErrorReturn { e -> Result.failure(e.message ?: "unknown", e) }
.observeOn(schedulerProvider.ui())
}.toObservable().startWith(Result.inProgress())
toObservable().toResult(schedulerProvider)
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ class SearchFragment : Fragment(), Injectable {

private fun setupSearch() {
setupRecyclerView()
searchViewModel.sessions.observe(this, { result ->
searchViewModel.result.observe(this, { result ->
when (result) {
is Result.Success -> {
val sessions = result.data
val sessions = result.data.sessions
sessionsGroup.updateSessions(sessions, onFavoriteClickListener)
binding.sessionsRecycler.scrollToPosition(0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class SearchTopicsFragment : Fragment(), Injectable {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
// TODO: searchTopicsViewModel.sessions fetch data here
// TODO: searchTopicsViewModel.result fetch data here
lifecycle.addObserver(searchTopicsViewModel)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.github.droidkaigi.confsched2018.data.repository.SessionRepository
import io.github.droidkaigi.confsched2018.model.SearchResult
import io.github.droidkaigi.confsched2018.model.Session
import io.github.droidkaigi.confsched2018.model.Speaker
import io.github.droidkaigi.confsched2018.presentation.Result
import io.github.droidkaigi.confsched2018.presentation.common.mapper.toResult
import io.github.droidkaigi.confsched2018.util.defaultErrorHandler
Expand All @@ -20,24 +20,15 @@ class SearchViewModel @Inject constructor(
private val repository: SessionRepository,
private val schedulerProvider: SchedulerProvider
) : ViewModel(), LifecycleObserver {
val sessions: MutableLiveData<Result<List<Session>>> = MutableLiveData()
val speakers: MutableLiveData<Result<List<Speaker>>> = MutableLiveData()
val result: MutableLiveData<Result<SearchResult>> = MutableLiveData()
private val compositeDisposable: CompositeDisposable = CompositeDisposable()

fun onQuery(query: String) {
repository.sessions
.map {
it.filter { it.title.contains(query) || it.desc.contains(query) }
}
repository.search(query)
.toResult(schedulerProvider)
.subscribe { sessions.value = it }
.addTo(compositeDisposable)
repository.speakers
.map {
it.filter { it.name.contains(query) }
.subscribe {
result.value = it
}
.toResult(schedulerProvider)
.subscribe { speakers.value = it }
.addTo(compositeDisposable)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
package io.github.droidkaigi.confsched2018

import io.github.droidkaigi.confsched2018.data.db.entity.*
import io.github.droidkaigi.confsched2018.model.*
import org.threeten.bp.LocalDateTime

val DUMMY_SESSION_ID_1 = "test1"
val DUMMY_SESSION_ID_2 = "test2"
const val DUMMY_SESSION_ID1 = "test1"
const val DUMMY_SESSION_ID2 = "test2"
const val DUMMY_SESSION_TITLE1 = "DroidKaigi"
const val DUMMY_SESSION_TITLE2 = "RejectKaigi"

fun createDummySessions(): List<Session> =
listOf(createDummySession(DUMMY_SESSION_ID_1), createDummySession(DUMMY_SESSION_ID_2))

fun createDummySession(sessionId: String): Session = Session(
id = sessionId,
title = "DroidKaigi",
desc = "How to create DroidKaigi app",
startTime = parseDate(10000),
endTime = parseDate(10000),
format = "30分",
room = Room(1, "Hall"),
topic = Topic(2, "Development tool"),
language = "JA",
level = Level(1, "Beginner"),
speakers = listOf(
createSpeaker(),
createSpeaker()
),
isFavorited = true
)

private fun createSpeaker(): Speaker {
listOf(
createDummySession(DUMMY_SESSION_ID1, DUMMY_SESSION_TITLE1),
createDummySession(DUMMY_SESSION_ID2, DUMMY_SESSION_TITLE2)
)

fun createDummySession(sessionId: String = DUMMY_SESSION_ID1, title: String = DUMMY_SESSION_TITLE1): Session {
return Session(
id = sessionId,
title = title,
desc = "How to create DroidKaigi app",
startTime = parseDate(10000),
endTime = parseDate(10000),
format = "30分",
room = Room(1, "Hall"),
topic = Topic(2, "Development tool"),
language = "JA",
level = Level(1, "Beginner"),
speakers = listOf(
createDummySpeaker(),
createDummySpeaker()
),
isFavorited = true
)
}

fun createDummySpeaker(): Speaker {
return Speaker(
name = "tm",
imageUrl = "http://example.com",
Expand All @@ -36,3 +45,52 @@ private fun createSpeaker(): Speaker {
companyUrl = null
)
}


fun createDummySpeakerEntities(): List<SpeakerEntity> {
return listOf(
SpeakerEntity(
"aaaa"
, "hogehoge"
, "https://example.com"
, "http://example.com/hoge"
, null
, null
, "http://example.github.com/hoge"
),
SpeakerEntity(
"bbbb"
, "hogehuga"
, "https://example.com"
, "http://example.com/hoge"
, null
, null
, "http://example.github.com/hoge"
))
}

fun createDummySessionWithSpeakersEntities(): List<SessionWithSpeakers> {
return listOf(SessionWithSpeakers(SessionEntity(DUMMY_SESSION_ID1,
DUMMY_SESSION_TITLE1,
"Endless battle",
LocalDateTime.of(1, 1, 1, 1, 1),
LocalDateTime.of(1, 1, 1, 1, 1),
"30分",
"日本語",
LevelEntity(1, "ニッチ / Niche"),
TopicEntity(1, "開発環境 / Development"),
RoomEntity(1, "ホール")),
listOf("aaaa", "bbbb")),
SessionWithSpeakers(SessionEntity(DUMMY_SESSION_ID2,
DUMMY_SESSION_TITLE2,
"Endless battle",
LocalDateTime.of(1, 1, 1, 1, 1),
LocalDateTime.of(1, 1, 1, 1, 1),
"30分",
"日本語",
LevelEntity(1, "ニッチ / Niche"),
TopicEntity(1, "開発環境 / Development"),
RoomEntity(1, "ホール")),
listOf("aaaa", "bbbb"))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import io.github.droidkaigi.confsched2018.DUMMY_SESSION_TITLE1
import io.github.droidkaigi.confsched2018.createDummySessionWithSpeakersEntities
import io.github.droidkaigi.confsched2018.createDummySpeakerEntities
import io.github.droidkaigi.confsched2018.data.db.FavoriteDatabase
import io.github.droidkaigi.confsched2018.data.db.SessionDatabase
import io.github.droidkaigi.confsched2018.data.db.entity.*
import io.github.droidkaigi.confsched2018.data.db.entity.RoomEntity
import io.github.droidkaigi.confsched2018.data.db.entity.mapper.toRooms
import io.github.droidkaigi.confsched2018.data.db.entity.mapper.toSession
import io.github.droidkaigi.confsched2018.model.SearchResult
import io.github.droidkaigi.confsched2018.util.rx.TestSchedulerProvider
import io.reactivex.Flowable
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.robolectric.RobolectricTestRunner
import org.threeten.bp.LocalDateTime

@RunWith(RobolectricTestRunner::class)
class SessionsDataRepositoryTest {
Expand Down Expand Up @@ -47,39 +50,8 @@ class SessionsDataRepositoryTest {
}

@Test fun sessions() {
val sessions = listOf(SessionWithSpeakers(SessionEntity("10"
, "DroidKaigi app"
, "Endless battle"
, LocalDateTime.of(1, 1, 1, 1, 1)
, LocalDateTime.of(1, 1, 1, 1, 1)
, "30分"
, "日本語"
, LevelEntity(1, "ニッチ / Niche")
, TopicEntity(1, "開発環境 / Development")
, RoomEntity(1, "ホール")),
listOf(
"aaaa", "bbbb"
)))
val speakers = listOf(
SpeakerEntity(
"aaaa"
, "hogehoge"
, "https://example.com"
, "http://example.com/hoge"
, null
, null
, "http://example.github.com/hoge"
),
SpeakerEntity(
"bbbb"
, "hogehuga"
, "https://example.com"
, "http://example.com/hoge"
, null
, null
, "http://example.github.com/hoge"
))

val sessions = createDummySessionWithSpeakersEntities()
val speakers = createDummySpeakerEntities()

whenever(sessionDatabase.getAllSessions()).doReturn(Flowable.just(sessions))
whenever(sessionDatabase.getAllSpeaker()).doReturn(Flowable.just(speakers))
Expand All @@ -96,4 +68,25 @@ class SessionsDataRepositoryTest {
verify(sessionDatabase).getAllSessions()
}

@Test fun search() {
val sessions = createDummySessionWithSpeakersEntities()
val speakers = createDummySpeakerEntities()
whenever(sessionDatabase.getAllSessions()).doReturn(Flowable.just(sessions))
whenever(sessionDatabase.getAllSpeaker()).doReturn(Flowable.just(speakers))
val sessionDataRepository = SessionDataRepository(mock(),
sessionDatabase,
favoriteDatabase,
TestSchedulerProvider())

sessionDataRepository.search(DUMMY_SESSION_TITLE1)
.doOnSuccess {
println(it)
}
.test()
.assertValue(SearchResult(listOf(sessions[0].toSession(speakers, emptyList())),
listOf()))

verify(sessionDatabase).getAllSessions()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.droidkaigi.confsched2018.presentation.detail

import android.arch.lifecycle.Observer
import com.nhaarman.mockito_kotlin.*
import io.github.droidkaigi.confsched2018.DUMMY_SESSION_ID_1
import io.github.droidkaigi.confsched2018.DUMMY_SESSION_ID1
import io.github.droidkaigi.confsched2018.createDummySession
import io.github.droidkaigi.confsched2018.createDummySessions
import io.github.droidkaigi.confsched2018.data.repository.SessionRepository
Expand Down Expand Up @@ -43,13 +43,13 @@ class SessionDetailViewModelTest {
val sessions = createDummySessions()
whenever(repository.sessions).doReturn(Flowable.just(sessions))
viewModel = SessionDetailViewModel(repository, TestSchedulerProvider())
viewModel.sessionId = DUMMY_SESSION_ID_1
viewModel.sessionId = DUMMY_SESSION_ID1
val result: Observer<Result<Session>> = mock()

viewModel.session.observeForever(result)

verify(repository).sessions
verify(result).onChanged(Result.success(createDummySession(DUMMY_SESSION_ID_1)))
verify(result).onChanged(Result.success(createDummySession(DUMMY_SESSION_ID1)))
}

@Test fun sessions_Error() {
Expand Down
Loading

0 comments on commit 65949f1

Please sign in to comment.