Skip to content

Commit ec624bd

Browse files
author
Melih Aksoy
committed
Experimental api message fixes, paging source & factory tests
1 parent c281e06 commit ec624bd

File tree

28 files changed

+294
-60
lines changed

28 files changed

+294
-60
lines changed

core/src/main/kotlin/com/melih/core/base/lifecycle/BaseActivity.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import androidx.databinding.ViewDataBinding
88
import androidx.navigation.fragment.NavHostFragment
99
import androidx.navigation.ui.NavigationUI
1010
import dagger.android.support.DaggerAppCompatActivity
11-
import kotlinx.coroutines.ExperimentalCoroutinesApi
1211

1312
const val NAV_HOST_FRAGMENT_TAG = "nav_host_fragment_tag"
1413

@@ -20,7 +19,6 @@ abstract class BaseActivity<T : ViewDataBinding> : DaggerAppCompatActivity() {
2019
protected lateinit var binding: T
2120
protected lateinit var navHostFragment: NavHostFragment
2221

23-
@ExperimentalCoroutinesApi
2422
override fun onCreate(savedInstanceState: Bundle?) {
2523
super.onCreate(savedInstanceState)
2624

core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:UseExperimental(ExperimentalCoroutinesApi::class)
2+
13
package com.melih.core.base.paging
24

35
import androidx.annotation.CallSuper
@@ -37,7 +39,6 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
3739

3840
// region Abstractions
3941

40-
@ExperimentalCoroutinesApi
4142
abstract fun loadDataForPage(page: Int): Flow<Result<List<T>>> // Load next page(s)
4243
// endregion
4344

@@ -62,7 +63,6 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
6263

6364
// region Functions
6465

65-
@ExperimentalCoroutinesApi
6666
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, T>) {
6767
// Looping through channel as we'll receive any state, error or data here
6868
loadDataForPage(INITIAL_PAGE)
@@ -81,7 +81,6 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
8181
.launchIn(coroutineScope)
8282
}
8383

84-
@ExperimentalCoroutinesApi
8584
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
8685
// Key for which page to load is in params
8786
val page = params.key
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.melih.core.base.viewmodel
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.Transformations
5+
import androidx.lifecycle.ViewModel
6+
import androidx.paging.LivePagedListBuilder
7+
import androidx.paging.PagedList
8+
import com.melih.core.base.paging.BasePagingFactory
9+
import com.melih.repository.interactors.base.Reason
10+
import com.melih.repository.interactors.base.State
11+
12+
/**
13+
* Base [ViewModel] for view models that will use [PagedList].
14+
*
15+
* Since data handling is done via [be.mediahuis.core.base.paging.BasePagingDataSource], this view model doesn't need
16+
* a [kotlinx.coroutines.channels.ReceiveChannel] and will not provide any default operations of data, but instead will
17+
* provde [pagedList] which should be observed and submitted.
18+
*
19+
* If paging won't be used, use [BaseViewModel] instead.
20+
*/
21+
abstract class BasePagingViewModel<T> : ViewModel() {
22+
23+
// region Abstractions
24+
25+
abstract val factory: BasePagingFactory<T>
26+
abstract val config: PagedList.Config
27+
private lateinit var _pagedList: LiveData<PagedList<T>>
28+
// endregion
29+
30+
// region Properties
31+
32+
/**
33+
* Observe [stateData] to get notified of state of data
34+
*/
35+
val stateData: LiveData<State> by lazy {
36+
Transformations.switchMap(factory.currentSource) {
37+
it.stateData
38+
}
39+
}
40+
41+
/**
42+
* Observe [errorData] to get notified if an error occurs
43+
*/
44+
val errorData: LiveData<Reason> by lazy {
45+
Transformations.switchMap(factory.currentSource) {
46+
it.reasonData
47+
}
48+
}
49+
50+
/**
51+
* Observe [pagedList] to submit list it provides
52+
*/
53+
val pagedList: LiveData<PagedList<T>> by lazy {
54+
LivePagedListBuilder(factory, config)
55+
.build()
56+
}
57+
// endregion
58+
59+
fun refresh() {
60+
factory.currentSource.value?.invalidate()
61+
}
62+
}

core/src/test/kotlin/com/melih/core/BaseTestWithMainThread.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:UseExperimental(ExperimentalCoroutinesApi::class)
2+
13
package com.melih.core
24

35
import androidx.arch.core.executor.ArchTaskExecutor
@@ -15,11 +17,9 @@ import kotlin.coroutines.suspendCoroutine
1517

1618
abstract class BaseTestWithMainThread {
1719

18-
@ExperimentalCoroutinesApi
1920
private val dispatcher = TestCoroutineDispatcher()
2021

2122
@BeforeEach
22-
@ExperimentalCoroutinesApi
2323
fun setUp() {
2424
Dispatchers.setMain(dispatcher)
2525
ArchTaskExecutor.getInstance()
@@ -33,7 +33,6 @@ abstract class BaseTestWithMainThread {
3333
}
3434

3535
@AfterEach
36-
@ExperimentalCoroutinesApi
3736
fun tearDown() {
3837
ArchTaskExecutor.getInstance()
3938
.setDelegate(null)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
@file:UseExperimental(ExperimentalCoroutinesApi::class)
2+
3+
package com.melih.core.paging
4+
5+
import androidx.paging.PageKeyedDataSource
6+
import com.melih.core.BaseTestWithMainThread
7+
import com.melih.core.base.paging.BasePagingDataSource
8+
import com.melih.core.testObserve
9+
import com.melih.repository.interactors.base.Failure
10+
import com.melih.repository.interactors.base.GenericError
11+
import com.melih.repository.interactors.base.Result
12+
import com.melih.repository.interactors.base.State
13+
import com.melih.repository.interactors.base.Success
14+
import io.mockk.mockk
15+
import io.mockk.spyk
16+
import io.mockk.verify
17+
import kotlinx.coroutines.ExperimentalCoroutinesApi
18+
import kotlinx.coroutines.flow.Flow
19+
import kotlinx.coroutines.flow.flow
20+
import kotlinx.coroutines.runBlocking
21+
import org.amshove.kluent.shouldBeInstanceOf
22+
import org.junit.jupiter.api.Nested
23+
import org.junit.jupiter.api.Test
24+
25+
class BasePagingDataSourceTest : BaseTestWithMainThread() {
26+
27+
val source = spyk(TestSource())
28+
val failureSource = spyk(TestFailureSource())
29+
30+
val data = 10
31+
val errorMessage = "Generic Error"
32+
33+
@Nested
34+
inner class BasePagingSource {
35+
36+
@Nested
37+
inner class LoadInitial {
38+
39+
@Test
40+
41+
fun `should update state accordingly`() {
42+
val params = mockk<PageKeyedDataSource.LoadInitialParams<Int>>(relaxed = true)
43+
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
44+
45+
runBlocking {
46+
47+
// Fake loading
48+
source.loadInitial(params, callback)
49+
50+
source.stateData.testObserve {
51+
it shouldBeInstanceOf State.Loading::class
52+
}
53+
}
54+
}
55+
56+
@Test
57+
58+
fun `should update error Error accordingly`() {
59+
val params = PageKeyedDataSource.LoadInitialParams<Int>(10, false)
60+
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
61+
62+
runBlocking {
63+
64+
// Fake loading
65+
failureSource.loadInitial(params, callback)
66+
67+
failureSource.reasonData.testObserve {
68+
it shouldBeInstanceOf GenericError::class
69+
}
70+
}
71+
}
72+
}
73+
74+
@Nested
75+
inner class LoadAfter {
76+
77+
@Test
78+
79+
fun `should update state accordingly`() {
80+
val params = PageKeyedDataSource.LoadParams(2, 10)
81+
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
82+
83+
runBlocking {
84+
85+
// Fake loading
86+
source.loadAfter(params, callback)
87+
88+
source.stateData.testObserve {
89+
it shouldBeInstanceOf State.Loading::class
90+
}
91+
}
92+
}
93+
94+
@Test
95+
96+
fun `should update error Error accordingly`() {
97+
val params = PageKeyedDataSource.LoadParams(2, 10)
98+
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
99+
100+
runBlocking {
101+
102+
// Fake loading
103+
failureSource.loadAfter(params, callback)
104+
105+
failureSource.reasonData.testObserve {
106+
it shouldBeInstanceOf GenericError::class
107+
}
108+
}
109+
}
110+
}
111+
112+
@Test
113+
114+
fun `should use loadDataForPage in loadInitial and transform emmited value`() {
115+
val params = mockk<PageKeyedDataSource.LoadInitialParams<Int>>(relaxed = true)
116+
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
117+
118+
// Fake loading
119+
source.loadInitial(params, callback)
120+
121+
// Make sure load initial called only once
122+
verify(exactly = 1) { source.loadDataForPage(any()) }
123+
124+
// Notified callback
125+
verify(exactly = 1) { callback.onResult(any(), any(), any()) }
126+
}
127+
128+
@Test
129+
130+
fun `should use loadDataForPage in loadAfter and transform emmited value`() {
131+
val params = PageKeyedDataSource.LoadParams(2, 10)
132+
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
133+
134+
// Fake loading
135+
source.loadAfter(params, callback)
136+
137+
// Make sure load initial called only once
138+
verify(exactly = 1) { source.loadDataForPage(any()) }
139+
140+
// Notified callback
141+
verify(exactly = 1) { callback.onResult(any(), any()) }
142+
}
143+
}
144+
145+
inner class TestSource : BasePagingDataSource<Int>() {
146+
147+
148+
val result = flow {
149+
emit(State.Loading())
150+
emit(Success(listOf(data)))
151+
}
152+
153+
154+
override fun loadDataForPage(page: Int): Flow<Result<List<Int>>> = result
155+
}
156+
157+
inner class TestFailureSource : BasePagingDataSource<Int>() {
158+
159+
val result = flow {
160+
emit(State.Loading())
161+
emit(Failure(GenericError()))
162+
}
163+
164+
override fun loadDataForPage(page: Int): Flow<Result<List<Int>>> = result
165+
}
166+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.melih.core.paging
2+
3+
import com.melih.core.BaseTestWithMainThread
4+
import com.melih.core.base.paging.BasePagingDataSource
5+
import com.melih.core.base.paging.BasePagingFactory
6+
import com.melih.core.testObserve
7+
import io.mockk.mockk
8+
import io.mockk.spyk
9+
import kotlinx.coroutines.runBlocking
10+
import org.amshove.kluent.shouldEqual
11+
import org.junit.jupiter.api.Test
12+
13+
class BasePagingFactoryTest : BaseTestWithMainThread() {
14+
15+
val factory = spyk(TestFactory())
16+
17+
@Test
18+
fun `create should update current source when it creates a new one`() {
19+
val source = factory.create()
20+
21+
runBlocking {
22+
factory.currentSource.testObserve {
23+
it shouldEqual source
24+
}
25+
}
26+
}
27+
28+
inner class TestFactory : BasePagingFactory<String>() {
29+
30+
override fun createSource(): BasePagingDataSource<String> = mockk(relaxed = true)
31+
32+
}
33+
}

features/detail/jacoco.exec

482 Bytes
Binary file not shown.

features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailBinds.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.melih.detail.ui.DetailViewModel
66
import dagger.Binds
77
import dagger.Module
88
import dagger.multibindings.IntoMap
9-
import kotlinx.coroutines.ExperimentalCoroutinesApi
109

1110
@Module
1211
abstract class DetailBinds {
@@ -16,7 +15,6 @@ abstract class DetailBinds {
1615
@Binds
1716
@IntoMap
1817
@ViewModelKey(DetailViewModel::class)
19-
@ExperimentalCoroutinesApi
2018
abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel
2119
// endregion
2220
}

features/detail/src/main/kotlin/com/melih/detail/ui/DetailActivity.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@ import com.melih.core.actions.EXTRA_LAUNCH_ID
66
import com.melih.core.base.lifecycle.BaseActivity
77
import com.melih.detail.R
88
import com.melih.detail.databinding.DetailActivityBinding
9-
import kotlinx.coroutines.ExperimentalCoroutinesApi
109

1110
const val INVALID_LAUNCH_ID = -1L
1211

1312
class DetailActivity : BaseActivity<DetailActivityBinding>() {
1413

1514
// region Functions
1615

17-
@ExperimentalCoroutinesApi
1816
override fun onCreate(savedInstanceState: Bundle?) {
1917
super.onCreate(savedInstanceState)
2018

features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,17 @@ import com.melih.core.extensions.createFor
88
import com.melih.core.extensions.observe
99
import com.melih.detail.R
1010
import com.melih.detail.databinding.DetailBinding
11-
import kotlinx.coroutines.ExperimentalCoroutinesApi
1211

1312
class DetailFragment : BaseDaggerFragment<DetailBinding>() {
1413

1514
// region Properties
1615

17-
@ExperimentalCoroutinesApi
1816
private val viewModel: DetailViewModel
1917
get() = viewModelFactory.createFor(this)
2018
// endregion
2119

2220
// region Functions
2321

24-
@ExperimentalCoroutinesApi
2522
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
2623
super.onViewCreated(view, savedInstanceState)
2724

0 commit comments

Comments
 (0)