WIP: feature/abstractions (#45)

* Abstraction layer backup

* Removed DataEntity, was unnecessary for now

* Separated network, persistence, entities and interaction, closes #29

* Renamed binding

* Removed build files, example tests

Removed build files, example tests

* Fixed build files were not being ignored all around app

* Updated CI ymls

* Small changes

* Fixed legacy repository package names

* Fixed CQ findings

* Updated Fastlane

* Packaging changes and version upgrades

* Removed core from interactors

* Version bumps

* Added new module graph
This commit is contained in:
Melih Aksoy
2019-10-30 17:27:53 +01:00
committed by GitHub
parent 83e39400a9
commit 88022629e1
103 changed files with 1098 additions and 921 deletions

View File

@@ -9,8 +9,8 @@ import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
import com.melih.abstractions.deliverable.Reason
import com.melih.core.R
import com.melih.repository.interactors.base.Reason
/**
* Parent of all fragments.

View File

@@ -4,12 +4,13 @@ import androidx.annotation.CallSuper
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.PageKeyedDataSource
import com.melih.repository.interactors.base.Reason
import com.melih.repository.interactors.base.Result
import com.melih.repository.interactors.base.State
import com.melih.repository.interactors.base.onFailure
import com.melih.repository.interactors.base.onState
import com.melih.repository.interactors.base.onSuccess
import com.melih.abstractions.data.ViewEntity
import com.melih.abstractions.deliverable.Reason
import com.melih.abstractions.deliverable.Result
import com.melih.abstractions.deliverable.State
import com.melih.abstractions.deliverable.onFailure
import com.melih.abstractions.deliverable.onState
import com.melih.abstractions.deliverable.onSuccess
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,11 +36,11 @@ const val INITIAL_PAGE = 0
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
abstract class BasePagingDataSource<R : ViewEntity> : PageKeyedDataSource<Int, R>() {
//region Abstractions
abstract fun loadDataForPage(page: Int): Flow<Result<List<T>>> // Load next page(s)
abstract fun loadDataForPage(page: Int): Flow<Result<List<R>>> // Load next page(s)
//endregion
//region Properties
@@ -63,7 +64,10 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
//region Functions
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, T>) {
override fun loadInitial(
params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, R>
) {
// Looping through channel as we'll receive any state, error or data here
loadDataForPage(INITIAL_PAGE)
.onEach { result ->
@@ -81,7 +85,7 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
.launchIn(coroutineScope)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, R>) {
// Key for which page to load is in params
val page = params.key
@@ -104,7 +108,7 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
/**
* This loads previous pages, we don't have a use for it yet, so it's a no-op override
*/
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, T>) {
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, R>) {
// no-op
}

View File

@@ -3,6 +3,7 @@ package com.melih.core.base.paging
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import com.melih.abstractions.data.ViewEntity
/**
* Base [factory][DataSource.Factory] class for any [dataSource][DataSource]s in project.
@@ -14,7 +15,7 @@ import androidx.paging.DataSource
*
* Purpose of this transmission is to encapuslate [basePagingDataSource][BasePagingDataSource].
*/
abstract class BasePagingFactory<T> : DataSource.Factory<Int, T>() {
abstract class BasePagingFactory<T : ViewEntity> : DataSource.Factory<Int, T>() {
//region Abstractions

View File

@@ -5,9 +5,10 @@ import androidx.lifecycle.Transformations.switchMap
import androidx.lifecycle.ViewModel
import androidx.paging.PagedList
import androidx.paging.toLiveData
import com.melih.abstractions.data.ViewEntity
import com.melih.abstractions.deliverable.Reason
import com.melih.abstractions.deliverable.State
import com.melih.core.base.paging.BasePagingFactory
import com.melih.repository.interactors.base.Reason
import com.melih.repository.interactors.base.State
/**
* Base [ViewModel] for view models that will use [PagedList].
@@ -18,7 +19,7 @@ import com.melih.repository.interactors.base.State
*
* If paging won't be used, use [BaseViewModel] instead.
*/
abstract class BasePagingViewModel<T> : ViewModel() {
abstract class BasePagingViewModel<T : ViewEntity> : ViewModel() {
//region Abstractions

View File

@@ -4,8 +4,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.melih.repository.interactors.base.Reason
import com.melih.repository.interactors.base.State
import com.melih.abstractions.deliverable.Reason
import com.melih.abstractions.deliverable.State
import kotlinx.coroutines.launch
/**

View File

@@ -1,32 +0,0 @@
package com.melih.core.extensions
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import com.melih.core.utils.ClearFocusQueryTextListener
/**
* Shorthand for [contains] with ignoreCase set [true]
*/
fun CharSequence.containsIgnoreCase(other: CharSequence) = contains(other, true)
/**
* Adds [ClearFocusQueryTextListener] as [SearchView.OnQueryTextListener]
*/
fun SearchView.setOnQueryChangedListener(block: (String?) -> Unit) = setOnQueryTextListener(ClearFocusQueryTextListener(this, block))
/**
* Shortening set menu item expands / collapses
*/
fun MenuItem.onExpandOrCollapse(onExpand: () -> Unit, onCollapse: () -> Unit) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
onCollapse()
return true
}
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
onExpand()
return true
}
})
}

View File

@@ -3,14 +3,15 @@
package com.melih.core.paging
import androidx.paging.PageKeyedDataSource
import com.melih.abstractions.data.ViewEntity
import com.melih.abstractions.deliverable.Failure
import com.melih.abstractions.deliverable.Reason
import com.melih.abstractions.deliverable.Result
import com.melih.abstractions.deliverable.State
import com.melih.abstractions.deliverable.Success
import com.melih.core.BaseTestWithMainThread
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.testObserve
import com.melih.repository.interactors.base.Failure
import com.melih.repository.interactors.base.GenericError
import com.melih.repository.interactors.base.Result
import com.melih.repository.interactors.base.State
import com.melih.repository.interactors.base.Success
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
@@ -28,7 +29,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
val failureSource = spyk(TestFailureSource())
val data = 10
val errorMessage = "Generic Error"
val errorMessageResId = 1313
@Nested
inner class BasePagingSource {
@@ -37,10 +38,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
inner class LoadInitial {
@Test
fun `should update state accordingly`() {
val params = mockk<PageKeyedDataSource.LoadInitialParams<Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, TestViewEntity>>(relaxed = true)
runBlocking {
@@ -54,10 +54,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
fun `should update error Error accordingly`() {
val params = PageKeyedDataSource.LoadInitialParams<Int>(10, false)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, TestViewEntity>>(relaxed = true)
runBlocking {
@@ -65,7 +64,7 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
failureSource.loadInitial(params, callback)
failureSource.reasonData.testObserve {
it shouldBeInstanceOf GenericError::class
it shouldBeInstanceOf TestFailureReason::class
}
}
}
@@ -75,10 +74,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
inner class LoadAfter {
@Test
fun `should update state accordingly`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, TestViewEntity>>(relaxed = true)
runBlocking {
@@ -92,10 +90,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
fun `should update error Error accordingly`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, TestViewEntity>>(relaxed = true)
runBlocking {
@@ -103,17 +100,16 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
failureSource.loadAfter(params, callback)
failureSource.reasonData.testObserve {
it shouldBeInstanceOf GenericError::class
it shouldBeInstanceOf TestFailureReason::class
}
}
}
}
@Test
fun `should use loadDataForPage in loadInitial and transform emmited value`() {
val params = mockk<PageKeyedDataSource.LoadInitialParams<Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadInitialCallback<Int, TestViewEntity>>(relaxed = true)
// Fake loading
source.loadInitial(params, callback)
@@ -126,10 +122,9 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
@Test
fun `should use loadDataForPage in loadAfter and transform emmited value`() {
val params = PageKeyedDataSource.LoadParams(2, 10)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, Int>>(relaxed = true)
val callback = mockk<PageKeyedDataSource.LoadCallback<Int, TestViewEntity>>(relaxed = true)
// Fake loading
source.loadAfter(params, callback)
@@ -142,25 +137,27 @@ class BasePagingDataSourceTest : BaseTestWithMainThread() {
}
}
inner class TestSource : BasePagingDataSource<Int>() {
inner class TestSource : BasePagingDataSource<TestViewEntity>() {
val result = flow {
emit(State.Loading())
emit(Success(listOf(data)))
emit(Success(listOf(TestViewEntity(data))))
}
override fun loadDataForPage(page: Int): Flow<Result<List<Int>>> = result
override fun loadDataForPage(page: Int): Flow<Result<List<TestViewEntity>>> = result
}
inner class TestFailureSource : BasePagingDataSource<Int>() {
inner class TestFailureSource : BasePagingDataSource<TestViewEntity>() {
val result = flow {
emit(State.Loading())
emit(Failure(GenericError()))
emit(Failure(TestFailureReason(errorMessageResId)))
}
override fun loadDataForPage(page: Int): Flow<Result<List<Int>>> = result
override fun loadDataForPage(page: Int): Flow<Result<List<TestViewEntity>>> = result
}
}
inner class TestViewEntity(data: Int) : ViewEntity
inner class TestFailureReason(override val messageRes: Int) : Reason()
}

View File

@@ -1,5 +1,6 @@
package com.melih.core.paging
import com.melih.abstractions.data.ViewEntity
import com.melih.core.BaseTestWithMainThread
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.base.paging.BasePagingFactory
@@ -25,9 +26,10 @@ class BasePagingFactoryTest : BaseTestWithMainThread() {
}
}
inner class TestFactory : BasePagingFactory<String>() {
override fun createSource(): BasePagingDataSource<String> = mockk(relaxed = true)
inner class TestFactory : BasePagingFactory<TestViewEntity>() {
override fun createSource(): BasePagingDataSource<TestViewEntity> = mockk(relaxed = true)
}
inner class TestViewEntity : ViewEntity
}