Skip to content

Commit

Permalink
Merge pull request #56 from jageishi/use-coroutine-bookmarks
Browse files Browse the repository at this point in the history
ブックマーク一覧でコルーチンを使用する
  • Loading branch information
jageishi committed Jun 9, 2020
2 parents 6912629 + f9901bd commit ed1e202
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 349 deletions.
13 changes: 12 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ android {
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

lintOptions {
abortOnError false
}

dataBinding {
enabled = true
}
Expand All @@ -46,12 +55,14 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'

def okhttp_version = '4.2.0'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"

def retrofit_version = '2.6.1'
def retrofit_version = '2.8.1'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package org.ageage.eggplant.bookmarks
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.ageage.eggplant.common.enums.SortType
import org.ageage.eggplant.common.model.Bookmark
import org.ageage.eggplant.common.repository.BookmarkRepository
Expand All @@ -15,8 +15,6 @@ class BookmarksViewModel(
private val schedulerProvider: BaseSchedulerProvider
) : ViewModel() {

private val disposable = CompositeDisposable()

private val _isLoading = MutableLiveData<Boolean>()
val isLoading: MutableLiveData<Boolean>
get() = _isLoading
Expand All @@ -27,43 +25,34 @@ class BookmarksViewModel(

private var isAlreadyLoaded = false

override fun onCleared() {
super.onCleared()
disposable.clear()
}

fun loadBookmarks(url: String, sortType: SortType, forceLoad: Boolean = false) {
if (isAlreadyLoaded && !forceLoad) {
return
}
fun loadBookmarks(url: String, sortType: SortType, forceLoad: Boolean = false) =
viewModelScope.launch {
if (isAlreadyLoaded && !forceLoad) {
return@launch
}

_isLoading.value = true
try {
repository.fetchBookmarks(url).let {
_bookmarks.value =
when (sortType) {
SortType.POPULAR -> {
it.filter { bookmark -> bookmark.comment.isNotEmpty() }
.sortedByDescending { bookmark -> bookmark.yellowStarNumber }
.take(10)
}
SortType.RECENT -> {
it.filter { bookmark -> bookmark.comment.isNotEmpty() }
.sortedByDescending { bookmark -> bookmark.timeStamp }
}
}

_isLoading.value = true
repository
.fetchBookmarks(url)
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe({ bookmarkList ->
val sortedBookmarks = when (sortType) {
SortType.POPULAR -> {
bookmarkList
.filter { bookmark -> bookmark.comment.isNotEmpty() }
.sortedByDescending { bookmark -> bookmark.yellowStarNumber }
.take(10)
}
SortType.RECENT -> {
bookmarkList
.filter { bookmark -> bookmark.comment.isNotEmpty() }
.sortedByDescending { bookmark -> bookmark.timeStamp }
}
}

_bookmarks.value = sortedBookmarks
_isLoading.value = false
isAlreadyLoaded = true

}, {
} catch (e: Throwable) {
// TODO エラー処理を記述する
} finally {
_isLoading.value = false
})
.addTo(disposable)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package org.ageage.eggplant.common.api

import io.reactivex.Observable
import org.ageage.eggplant.common.api.response.BookmarkEntryResponse
import org.ageage.eggplant.common.api.response.BookmarkStarResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface BookmarkService {
@GET("/entry/jsonlite/")
fun bookmarkEntry(@Query("url") url: String): Observable<BookmarkEntryResponse>
suspend fun bookmarkEntry(@Query("url") url: String): BookmarkEntryResponse

@GET("/entry.json")
fun startCount(@Query("uri") url: String): Observable<BookmarkStarResponse>
suspend fun startCount(@Query("uri") url: String): BookmarkStarResponse
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.ageage.eggplant.common.repository

import android.text.format.DateFormat
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.toObservable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import org.ageage.eggplant.common.api.BookmarkService
import org.ageage.eggplant.common.api.Client
import org.ageage.eggplant.common.api.response.mapper.toBookmarks
Expand All @@ -15,46 +15,48 @@ import java.util.*

class BookmarkRepository {

fun fetchBookmarks(url: String): Observable<List<Bookmark>> {
suspend fun fetchBookmarks(url: String): List<Bookmark> {
val service =
Client.retrofitClient(Endpoint.HATENA_BOOKMARK)
.create(BookmarkService::class.java)

return service.bookmarkEntry(url)
.flatMap { bookmarkEntry ->
bookmarkEntry.bookmarkResponses.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
val list = mutableListOf<Bookmark>()
withContext(Dispatchers.IO) {
service.bookmarkEntry(url).let { bookmarkEntry ->
bookmarkEntry.bookmarkResponses
.filter {
it.comment.isNotEmpty()
}
.concatMapEager { bookmark ->
val timestamp =
DateFormat.format(
"yyyyMMdd",
SimpleDateFormat(
"yyyy/MM/dd HH:mm",
Locale.US
).parse(bookmark.timestamp)
)
Client.retrofitClient(Endpoint.HATENA_STAR)
.create(BookmarkService::class.java)
.startCount("${Endpoint.HATENA_BOOKMARK.url}/${bookmark.user}/${timestamp}#bookmark-${bookmarkEntry.eid}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { bookmark ->
async {
val timestamp =
DateFormat.format(
"yyyyMMdd",
SimpleDateFormat(
"yyyy/MM/dd HH:mm",
Locale.US
).parse(bookmark.timestamp)
)
Client.retrofitClient(Endpoint.HATENA_STAR)
.create(BookmarkService::class.java)
.startCount("${Endpoint.HATENA_BOOKMARK.url}/${bookmark.user}/${timestamp}#bookmark-${bookmarkEntry.eid}")
}
}
.toList()
.map { responses ->
.awaitAll()
.let { responses ->
bookmarkEntry.bookmarkResponses
.filter {
it.comment.isNotEmpty()
}
.forEachIndexed { index, bookmarkResponse ->

bookmarkResponse.entry = responses[index].entries.elementAtOrNull(0)
}
bookmarkEntry.bookmarkResponses.toBookmarks()
list.addAll(bookmarkEntry.bookmarkResponses.toBookmarks())
}
.toObservable()
}
}

return list
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.ageage.eggplant.bookmarks
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import com.nhaarman.mockitokotlin2.*
import io.reactivex.Observable
import org.ageage.eggplant.common.TestCoroutineRule
import org.ageage.eggplant.common.enums.SortType
import org.ageage.eggplant.common.model.Bookmark
import org.ageage.eggplant.common.repository.BookmarkRepository
Expand All @@ -21,7 +21,10 @@ import java.util.*
class BookmarksViewModelTest {

@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
val instantTaskExecutorRule = InstantTaskExecutorRule()

@get:Rule
val testCoroutineRule = TestCoroutineRule()

@Mock
private lateinit var bookmarksObserver: Observer<List<Bookmark>>
Expand All @@ -37,9 +40,9 @@ class BookmarksViewModelTest {
}

@Test
fun loodBookmarks_onSuccess_popular() {
fun loodBookmarks_onSuccess_popular() = testCoroutineRule.runBlockingTest {
val mockRepository = mock<BookmarkRepository> {
on { fetchBookmarks(url) } doReturn Observable.just(fakeBookmarks)
onBlocking { fetchBookmarks(url) } doReturn fakeBookmarks
}

val viewModel = BookmarksViewModel(mockRepository, TrampolineSchedulerProvider())
Expand All @@ -59,9 +62,9 @@ class BookmarksViewModelTest {
}

@Test
fun loadBookmarks_onSuccess_recent() {
fun loadBookmarks_onSuccess_recent() = testCoroutineRule.runBlockingTest {
val mockRepository = mock<BookmarkRepository> {
on { fetchBookmarks(url) } doReturn Observable.just(fakeBookmarks)
onBlocking { fetchBookmarks(url) } doReturn fakeBookmarks
}

val viewModel = BookmarksViewModel(mockRepository, TrampolineSchedulerProvider())
Expand All @@ -81,9 +84,9 @@ class BookmarksViewModelTest {
}

@Test
fun loadBookmarks_onError() {
fun loadBookmarks_onError() = testCoroutineRule.runBlockingTest {
val mockRepository = mock<BookmarkRepository> {
on { fetchBookmarks(url) } doReturn Observable.error(Throwable())
onBlocking { fetchBookmarks(url) } doThrow RuntimeException()
}

val viewModel = BookmarksViewModel(mockRepository, TrampolineSchedulerProvider())
Expand All @@ -101,9 +104,9 @@ class BookmarksViewModelTest {
}

@Test
fun loadBookmarks_twice() {
fun loadBookmarks_twice() = testCoroutineRule.runBlockingTest {
val mockRepository = mock<BookmarkRepository> {
on { fetchBookmarks(url) } doReturn Observable.just(fakeBookmarks)
onBlocking { fetchBookmarks(url) } doReturn fakeBookmarks
}

val viewModel = BookmarksViewModel(mockRepository, TrampolineSchedulerProvider())
Expand All @@ -116,9 +119,9 @@ class BookmarksViewModelTest {


@Test
fun loadBookmarks_twice_forcibly() {
fun loadBookmarks_twice_forcibly() = testCoroutineRule.runBlockingTest {
val mockRepository = mock<BookmarkRepository> {
on { fetchBookmarks(url) } doReturn Observable.just(fakeBookmarks)
onBlocking { fetchBookmarks(url) } doReturn fakeBookmarks
}

val viewModel = BookmarksViewModel(mockRepository, TrampolineSchedulerProvider())
Expand Down
26 changes: 26 additions & 0 deletions app/src/test/java/org/ageage/eggplant/common/TestCoroutineRule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.ageage.eggplant.common

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.*
import org.junit.rules.TestWatcher
import org.junit.runner.Description

class TestCoroutineRule : TestWatcher() {

private val testCoroutineDispatcher = TestCoroutineDispatcher()

override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testCoroutineDispatcher)
}

override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testCoroutineDispatcher.cleanupTestCoroutines()
}

fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) {
testCoroutineDispatcher.runBlockingTest { block() }
}
}

0 comments on commit ed1e202

Please sign in to comment.