mirror of
https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture.git
synced 2026-03-22 17:39:58 +01:00
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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user