Milestones/ms1 (#16)

* Closes #11

* Closes #13

* Closes #17

* Closes #18

* Closes #19

* Closes #6

* Closes #3

* Closes #12

* Closes #15
This commit is contained in:
Melihcan Aksoy
2019-07-26 13:38:51 +02:00
committed by Melih Aksoy
parent 11889446cb
commit 625776609d
103 changed files with 4367 additions and 716 deletions

View File

@@ -15,8 +15,5 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation testLibraries.jUnitApi
testImplementation testLibraries.mockk
testImplementation testLibraries.kluent
testImplementation testLibraries.coroutinesTest
}

BIN
features/detail/jacoco.exec Normal file

Binary file not shown.

View File

@@ -1,7 +1,9 @@
package com.melih.detail.di
import com.melih.detail.di.modules.DetailBinds
import com.melih.detail.di.modules.DetailProvides
import com.melih.detail.ui.DetailFragment
import com.melih.list.di.scopes.DetailFragmentScope
import dagger.Module
import dagger.android.ContributesAndroidInjector
@@ -15,9 +17,11 @@ abstract class DetailContributor {
@ContributesAndroidInjector(
modules = [
DetailBinds::class
DetailBinds::class,
DetailProvides::class
]
)
@DetailFragmentScope
abstract fun detailFragment(): DetailFragment
// endregion
}

View File

@@ -0,0 +1,24 @@
package com.melih.detail.di
import com.melih.detail.ui.DetailActivity
import com.melih.list.di.scopes.DetailScope
import dagger.Module
import dagger.android.ContributesAndroidInjector
/**
* Contributes fragments & view models in this module
*/
@Module
abstract class DetailModule {
// region Contributes
@ContributesAndroidInjector(
modules = [
DetailContributor::class
]
)
@DetailScope
abstract fun detailActivity(): DetailActivity
// endregion
}

View File

@@ -6,7 +6,6 @@ import com.melih.detail.ui.DetailViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import kotlinx.coroutines.ExperimentalCoroutinesApi
@Module
abstract class DetailBinds {
@@ -16,7 +15,6 @@ abstract class DetailBinds {
@Binds
@IntoMap
@ViewModelKey(DetailViewModel::class)
@ExperimentalCoroutinesApi
abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel
// endregion
}

View File

@@ -0,0 +1,21 @@
package com.melih.detail.di.modules
import androidx.navigation.fragment.navArgs
import com.melih.detail.ui.DetailFragment
import com.melih.detail.ui.DetailFragmentArgs
import com.melih.repository.interactors.GetLaunchDetails
import dagger.Module
import dagger.Provides
@Module
class DetailProvides {
/**
* Provides launch detail params
*/
@Provides
fun provideGetLaunchDetailParams(fragment: DetailFragment): GetLaunchDetails.Params {
val args: DetailFragmentArgs by fragment.navArgs()
return GetLaunchDetails.Params(args.launchId)
}
}

View File

@@ -0,0 +1,7 @@
package com.melih.list.di.scopes
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.BINARY)
annotation class DetailFragmentScope

View File

@@ -0,0 +1,7 @@
package com.melih.list.di.scopes
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.BINARY)
annotation class DetailScope

View File

@@ -6,7 +6,6 @@ import com.melih.core.actions.EXTRA_LAUNCH_ID
import com.melih.core.base.lifecycle.BaseActivity
import com.melih.detail.R
import com.melih.detail.databinding.DetailActivityBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
const val INVALID_LAUNCH_ID = -1L
@@ -14,13 +13,12 @@ class DetailActivity : BaseActivity<DetailActivityBinding>() {
// region Functions
@ExperimentalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true);
supportActionBar?.setDisplayShowHomeEnabled(true);
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
override fun getLayoutId(): Int = R.layout.activity_detail

View File

@@ -3,38 +3,28 @@ package com.melih.detail.ui
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.View
import androidx.navigation.fragment.navArgs
import com.melih.core.base.lifecycle.BaseDaggerFragment
import com.melih.core.extensions.createFor
import com.melih.core.extensions.observe
import com.melih.detail.R
import com.melih.detail.databinding.DetailBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import timber.log.Timber
class DetailFragment : BaseDaggerFragment<DetailBinding>() {
// region Properties
private val args: DetailFragmentArgs by navArgs()
@ExperimentalCoroutinesApi
private val viewModel: DetailViewModel
get() = viewModelFactory.createFor(this)
// endregion
// region Functions
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvDescription.movementMethod = ScrollingMovementMethod()
binding.viewModel = viewModel
viewModel.createParamsFor(args.launchId)
viewModel.loadData()
// Observing error to show toast with retry action
observe(viewModel.errorData) {
showSnackbarWithAction(it) {

View File

@@ -1,24 +1,20 @@
package com.melih.detail.ui
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import com.melih.core.base.viewmodel.BaseViewModel
import com.melih.repository.entities.LaunchEntity
import com.melih.repository.interactors.GetLaunchDetails
import kotlinx.coroutines.ExperimentalCoroutinesApi
import com.melih.repository.interactors.base.handle
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
@ExperimentalCoroutinesApi
class DetailViewModel @Inject constructor(
private val getLaunchDetails: GetLaunchDetails
private val getLaunchDetails: GetLaunchDetails,
private val getLaunchDetailsParams: GetLaunchDetails.Params
) : BaseViewModel<LaunchEntity>() {
// region Properties
private var params = GetLaunchDetails.Params(INVALID_LAUNCH_ID)
val rocketName = Transformations.map(successData) {
it.rocket.name
}
@@ -38,18 +34,12 @@ class DetailViewModel @Inject constructor(
// region Functions
fun createParamsFor(id: Long) {
params = GetLaunchDetails.Params(id)
}
/**
* Triggering interactor in view model scope
*/
override fun loadData() {
viewModelScope.launch {
getLaunchDetails(params).collect {
it.handle(::handleState, ::handleFailure, ::handleSuccess)
}
override suspend fun loadData() {
getLaunchDetails(getLaunchDetailsParams).collect {
it.handle(::handleState, ::handleFailure, ::handleSuccess)
}
}
// endregion

View File

@@ -1,4 +1,4 @@
package com.melih.list
package com.melih.detail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -8,19 +8,17 @@ import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@UseExperimental(ExperimentalCoroutinesApi::class)
abstract class BaseTestWithMainThread {
@ExperimentalCoroutinesApi
protected val dispatcher = TestCoroutineDispatcher()
@BeforeEach
@ExperimentalCoroutinesApi
fun setUp() {
Dispatchers.setMain(dispatcher)
}
@AfterEach
@ExperimentalCoroutinesApi
fun tearDown() {
Dispatchers.resetMain()
dispatcher.cleanupTestCoroutines()

View File

@@ -1,7 +1,6 @@
package com.melih.detail
import com.melih.detail.ui.DetailViewModel
import com.melih.list.BaseTestWithMainThread
import com.melih.repository.interactors.GetLaunchDetails
import io.mockk.mockk
import io.mockk.slot
@@ -17,21 +16,20 @@ import org.junit.jupiter.api.Test
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
class DetailViewModelTest : BaseTestWithMainThread() {
private val getLaunchDetails: GetLaunchDetails = mockk(relaxed = true)
private val getLaunchDetailsParams = GetLaunchDetails.Params(1013)
@ExperimentalCoroutinesApi
private val viewModel = spyk(DetailViewModel(getLaunchDetails))
private val viewModel = spyk(DetailViewModel(getLaunchDetails, getLaunchDetailsParams))
@Test
@ExperimentalCoroutinesApi
fun `loadData should invoke getLauchDetails with provided params`() {
dispatcher.runBlockingTest {
val paramsSlot = slot<GetLaunchDetails.Params>()
viewModel.createParamsFor(1013)
viewModel.loadData()
// init should have called it already due to creation above

View File

@@ -7,8 +7,7 @@ apply from: "$rootProject.projectDir/scripts/feature_module.gradle"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation testLibraries.jUnitApi
testImplementation testLibraries.mockk
testImplementation testLibraries.kluent
implementation libraries.paging
testImplementation testLibraries.coroutinesTest
}

Binary file not shown.

View File

@@ -2,6 +2,7 @@ package com.melih.list.di
import com.melih.list.di.modules.LaunchesBinds
import com.melih.list.di.modules.LaunchesProvides
import com.melih.list.di.scopes.LaunchesFragmentScope
import com.melih.list.ui.LaunchesFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@@ -20,6 +21,7 @@ abstract class LaunchesContributor {
LaunchesBinds::class
]
)
abstract fun listFragment(): LaunchesFragment
@LaunchesFragmentScope
abstract fun launchesFragment(): LaunchesFragment
// endregion
}

View File

@@ -0,0 +1,24 @@
package com.melih.list.di
import com.melih.list.di.scopes.LaunchesScope
import com.melih.list.ui.LaunchesActivity
import dagger.Module
import dagger.android.ContributesAndroidInjector
/**
* Contributes fragments & view models in this module
*/
@Module
abstract class LaunchesFeatureModule {
// region Contributes
@ContributesAndroidInjector(
modules = [
LaunchesContributor::class
]
)
@LaunchesScope
abstract fun launchesActivity(): LaunchesActivity
// endregion
}

View File

@@ -2,11 +2,10 @@ package com.melih.list.di.modules
import androidx.lifecycle.ViewModel
import com.melih.core.di.keys.ViewModelKey
import com.melih.list.ui.LaunchesViewModel
import com.melih.list.ui.vm.LaunchesViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import kotlinx.coroutines.ExperimentalCoroutinesApi
@Module
abstract class LaunchesBinds {
@@ -16,7 +15,6 @@ abstract class LaunchesBinds {
@Binds
@IntoMap
@ViewModelKey(LaunchesViewModel::class)
@ExperimentalCoroutinesApi
abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel
// endregion
}

View File

@@ -0,0 +1,24 @@
package com.melih.list.di.modules
import androidx.paging.Config
import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT
import com.melih.repository.interactors.GetLaunches
import dagger.Module
import dagger.Provides
@Module
class LaunchesProvides {
/**
* Provides lauches, using default value of 15
*/
@Provides
fun provideGetLaunchesParams() = GetLaunches.Params(page = 0)
@Provides
fun getPagingConfig() = Config(
DEFAULT_LAUNCHES_AMOUNT,
prefetchDistance = 2,
enablePlaceholders = false
)
}

View File

@@ -0,0 +1,7 @@
package com.melih.list.di.scopes
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.BINARY)
annotation class LaunchesFragmentScope

View File

@@ -0,0 +1,7 @@
package com.melih.list.di.scopes
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.BINARY)
annotation class LaunchesScope

View File

@@ -5,13 +5,11 @@ import androidx.navigation.fragment.NavHostFragment
import com.melih.core.base.lifecycle.BaseActivity
import com.melih.list.R
import com.melih.list.databinding.LaunchesActivityBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
class LaunchesActivity : BaseActivity<LaunchesActivityBinding>() {
// region Functions
@ExperimentalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -0,0 +1,97 @@
package com.melih.list.ui
import android.os.Bundle
import android.view.View
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.melih.core.actions.Actions
import com.melih.core.base.lifecycle.BaseDaggerFragment
import com.melih.core.extensions.createFor
import com.melih.core.extensions.observe
import com.melih.list.R
import com.melih.list.databinding.ListBinding
import com.melih.list.ui.adapters.LaunchesAdapter
import com.melih.list.ui.vm.LaunchesViewModel
import com.melih.repository.entities.LaunchEntity
import com.melih.repository.interactors.base.State
class LaunchesFragment : BaseDaggerFragment<ListBinding>(), SwipeRefreshLayout.OnRefreshListener {
// region Properties
private val viewModel: LaunchesViewModel
get() = viewModelFactory.createFor(this)
private val launchesAdapter = LaunchesAdapter(::onItemSelected)
// endregion
// region Functions
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//setHasOptionsMenu(true)
binding.rocketList.adapter = launchesAdapter
binding.swipeRefreshLayout.setOnRefreshListener(this)
observeDataChanges()
}
//override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// inflater.inflate(R.menu.menu_rocket_list, menu)
//
// with(menu.findItem(R.id.search)) {
// onExpandOrCollapse(::onSearchExpand, ::onSearchCollapse)
// setSearchQueryListener(actionView as SearchView)
// }
//
// super.onCreateOptionsMenu(menu, inflater)
//}
private fun observeDataChanges() {
// Observing state to show loading
observe(viewModel.stateData) {
binding.swipeRefreshLayout.isRefreshing = it is State.Loading
}
// Observing error to show toast with retry action
observe(viewModel.errorData) {
showSnackbarWithAction(it) {
viewModel.retry()
}
}
observe(viewModel.pagedList) {
launchesAdapter.submitList(it)
}
//observe(viewModel.filteredItems) {
// launchesAdapter.submitList(it)
//}
}
private fun onItemSelected(item: LaunchEntity?) {
startActivity(Actions.openDetailFor(item?.id ?: -1L))
}
//private fun onSearchExpand() {
// binding.swipeRefreshLayout.isEnabled = false
//}
//private fun onSearchCollapse() {
// binding.swipeRefreshLayout.isEnabled = true
//}
//private fun setSearchQueryListener(searchView: SearchView) {
// searchView.setOnQueryChangedListener {
// viewModel.filterItemListBy(it)
// }
//}
override fun onRefresh() {
viewModel.refresh()
}
override fun getLayoutId(): Int = R.layout.fragment_launches
// endregion
}

View File

@@ -1,14 +1,14 @@
package com.melih.list.ui
package com.melih.list.ui.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import com.melih.core.base.recycler.BaseListAdapter
import com.melih.core.base.recycler.BasePagingListAdapter
import com.melih.core.base.recycler.BaseViewHolder
import com.melih.list.databinding.LaunchRowBinding
import com.melih.repository.entities.LaunchEntity
class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BaseListAdapter<LaunchEntity>(
class LaunchesAdapter(itemClickListener: (LaunchEntity?) -> Unit) : BasePagingListAdapter<LaunchEntity>(
object : DiffUtil.ItemCallback<LaunchEntity>() {
override fun areItemsTheSame(oldItem: LaunchEntity, newItem: LaunchEntity): Boolean =
oldItem.id == newItem.id
@@ -30,11 +30,11 @@ class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BaseListAdapt
class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder<LaunchEntity>(binding) {
override fun bind(item: LaunchEntity) {
override fun bind(item: LaunchEntity?) {
binding.entity = item
val missions = item.missions
binding.tvDescription.text = if (missions.isNotEmpty()) missions[0].description else ""
val missions = item?.missions
binding.tvDescription.text = if (!missions.isNullOrEmpty()) missions[0].description else ""
binding.executePendingBindings()
}

View File

@@ -0,0 +1,26 @@
package com.melih.list.ui.paging
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.repository.entities.LaunchEntity
import com.melih.repository.interactors.GetLaunches
import com.melih.repository.interactors.base.Result
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class LaunchesPagingSource @Inject constructor(
private val getLaunches: GetLaunches,
private val getLaunchesParams: GetLaunches.Params
) : BasePagingDataSource<LaunchEntity>() {
//region Functions
@UseExperimental(ExperimentalCoroutinesApi::class)
override fun loadDataForPage(page: Int): Flow<Result<List<LaunchEntity>>> =
getLaunches(
getLaunchesParams.copy(
page = page
)
)
//endregion
}

View File

@@ -0,0 +1,14 @@
package com.melih.list.ui.paging
import com.melih.core.base.paging.BasePagingDataSource
import com.melih.core.base.paging.BasePagingFactory
import com.melih.repository.entities.LaunchEntity
import javax.inject.Inject
import javax.inject.Provider
class LaunchesPagingSourceFactory @Inject constructor(
private val sourceProvider: Provider<LaunchesPagingSource>
) : BasePagingFactory<LaunchEntity>() {
override fun createSource(): BasePagingDataSource<LaunchEntity> = sourceProvider.get()
}

View File

@@ -0,0 +1,45 @@
package com.melih.list.ui.vm
import androidx.paging.PagedList
import com.melih.core.base.paging.BasePagingFactory
import com.melih.core.base.viewmodel.BasePagingViewModel
import com.melih.list.ui.paging.LaunchesPagingSourceFactory
import com.melih.repository.entities.LaunchEntity
import javax.inject.Inject
class LaunchesViewModel @Inject constructor(
private val launchesPagingSourceFactory: LaunchesPagingSourceFactory,
private val launchesPagingConfig: PagedList.Config
) : BasePagingViewModel<LaunchEntity>() {
// region Properties
override val factory: BasePagingFactory<LaunchEntity>
get() = launchesPagingSourceFactory
override val config: PagedList.Config
get() = launchesPagingConfig
//private val _filteredItems = MediatorLiveData<PagedList<LaunchEntity>>()
//val filteredItems: LiveData<PagedList<LaunchEntity>>
// get() = _filteredItems
// endregion
//init {
// _filteredItems.addSource(pagedList, _filteredItems::setValue)
//}
// region Functions
//fun filterItemListBy(query: String?) {
//
// _filteredItems.value = if (!query.isNullOrBlank()) {
// pagedList.value
// ?.snapshot() as PagedList<LaunchEntity>
// } else {
// pagedList.value
// }
//}
// endregion
}

View File

@@ -8,7 +8,7 @@
<variable
name="viewModel"
type="com.melih.list.ui.LaunchesViewModel" />
type="com.melih.list.ui.vm.LaunchesViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout

View File

@@ -65,7 +65,7 @@
android:layout_marginLeft="@dimen/padding_standard"
android:layout_marginEnd="@dimen/padding_standard"
android:layout_marginRight="@dimen/padding_standard"
android:text="@{entity.rocket.name}"
android:text="@{entity.name}"
android:textAppearance="@style/TitleTextAppearance"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imgRocket"

View File

@@ -1,3 +1,5 @@
@file:UseExperimental(ExperimentalCoroutinesApi::class)
package com.melih.list
import kotlinx.coroutines.Dispatchers
@@ -10,17 +12,14 @@ import org.junit.jupiter.api.BeforeEach
abstract class BaseTestWithMainThread {
@ExperimentalCoroutinesApi
protected val dispatcher = TestCoroutineDispatcher()
@BeforeEach
@ExperimentalCoroutinesApi
fun setUp() {
Dispatchers.setMain(dispatcher)
}
@AfterEach
@ExperimentalCoroutinesApi
fun tearDown() {
Dispatchers.resetMain()
dispatcher.cleanupTestCoroutines()

View File

@@ -1,16 +0,0 @@
package com.melih.list.di.modules
import com.melih.list.ui.LaunchesAdapter
import com.melih.repository.interactors.GetLaunches
import dagger.Module
import dagger.Provides
@Module
class LaunchesProvides {
/**
* Provides lauches, using default value of 10
*/
@Provides
fun provideGetLaunchesParams() = GetLaunches.Params()
}

View File

@@ -1,100 +0,0 @@
package com.melih.list.ui
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.melih.core.actions.Actions
import com.melih.core.base.lifecycle.BaseDaggerFragment
import com.melih.core.extensions.containsIgnoreCase
import com.melih.core.extensions.createFor
import com.melih.core.extensions.observe
import com.melih.core.extensions.setOnQueryChangedListener
import com.melih.list.R
import com.melih.list.databinding.ListBinding
import com.melih.repository.entities.LaunchEntity
import com.melih.repository.interactors.base.Result
import kotlinx.coroutines.ExperimentalCoroutinesApi
class LaunchesFragment : BaseDaggerFragment<ListBinding>(), SwipeRefreshLayout.OnRefreshListener {
// region Properties
@ExperimentalCoroutinesApi
private val viewModel: LaunchesViewModel
get() = viewModelFactory.createFor(this)
private val launchesAdapter = LaunchesAdapter(::onItemSelected)
private val itemList = mutableListOf<LaunchEntity>()
// endregion
// region Functions
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
binding.rocketList.adapter = launchesAdapter
binding.swipeRefreshLayout.setOnRefreshListener(this)
observeDataChanges()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_rocket_list, menu)
setSearchQueryListener((menu.findItem(R.id.search).actionView as SearchView))
super.onCreateOptionsMenu(menu, inflater)
}
@ExperimentalCoroutinesApi
private fun observeDataChanges() {
// Observing state to show loading
observe(viewModel.stateData) {
binding.swipeRefreshLayout.isRefreshing = it is Result.State.Loading
}
// Observing error to show toast with retry action
observe(viewModel.errorData) {
showSnackbarWithAction(it) {
viewModel.retry()
}
}
observe(viewModel.successData) {
itemList.addAll(it)
launchesAdapter.submitList(itemList)
binding.rocketList.scheduleLayoutAnimation()
}
}
private fun onItemSelected(item: LaunchEntity) {
startActivity(Actions.openDetailFor(item.id))
}
private fun setSearchQueryListener(searchView: SearchView) {
searchView.setOnQueryChangedListener {
filterItemListBy(it)
}
}
private fun filterItemListBy(query: String?) =
if (!query.isNullOrBlank()) {
itemList.filter {
it.rocket.name.containsIgnoreCase(query) || it.missions.any { it.description.containsIgnoreCase(query) }
}
} else {
itemList
}
@ExperimentalCoroutinesApi
override fun onRefresh() {
viewModel.refresh()
}
override fun getLayoutId(): Int = R.layout.fragment_launches
// endregion
}

View File

@@ -1,38 +0,0 @@
package com.melih.list.ui
import androidx.lifecycle.viewModelScope
import com.melih.core.base.viewmodel.BaseViewModel
import com.melih.repository.entities.LaunchEntity
import com.melih.repository.interactors.GetLaunches
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import javax.inject.Inject
@ExperimentalCoroutinesApi
class LaunchesViewModel @Inject constructor(
private val getLaunches: GetLaunches,
private val getLaunchesParams: GetLaunches.Params
) : BaseViewModel<List<LaunchEntity>>() {
// region Initialization
init {
loadData()
}
// endregion
// region Functions
/**
* Triggering interactor in view model scope
*/
override fun loadData() {
viewModelScope.launch {
getLaunches(getLaunchesParams).collect {
it.handle(::handleState, ::handleFailure, ::handleSuccess)
}
}
}
// endregion
}

View File

@@ -1,33 +0,0 @@
package com.melih.list
import com.melih.list.ui.LaunchesViewModel
import com.melih.repository.interactors.GetLaunches
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class LaunchesViewModelTest : BaseTestWithMainThread() {
val getLaunches: GetLaunches = mockk(relaxed = true)
val getLaunchesParams: GetLaunches.Params = mockk(relaxed = true)
@Test
@ExperimentalCoroutinesApi
fun `loadData should invoke getLauches with provided params`() {
spyk(LaunchesViewModel(getLaunches, getLaunchesParams))
dispatcher.runBlockingTest {
// init should have called it already due to creation above
verify(exactly = 1) { getLaunches(getLaunchesParams) }
}
}
}