diff --git a/README.md b/README.md index 1e7210b..f4bd56d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Rocket Science +# Android-Kotlin-Modulerized-CleanArchitecture [![Actions](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/workflows/Android%20CI/badge.svg)](https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/actions) [![CircleCI](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master.svg?style=svg)](https://circleci.com/gh/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture/tree/master) [![codebeat badge](https://codebeat.co/badges/542fb08a-b3cc-4ff8-b1bb-35a66932f12f)](https://codebeat.co/projects/github-com-melihaksoy-rocketscience-master) [![codecov](https://codecov.io/gh/melihaksoy/RocketScience/branch/master/graph/badge.svg?token=pXPKpV5dz6)](https://codecov.io/gh/melihaksoy/RocketScience) [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) -RocketScience is a prototype application tries to serve an example for modularization & clean architecture in Android. +This is a prototype application tries to serve an example for modularization & clean architecture in Android. While there are many blogs about good practices, it's hard to come by a complete example that merges these different popular topics & approaches. -RocketScience takes popular approaches and libraries to create an example on how to actually bind these components with each other. +This project takes popular approaches and libraries to create an example on how to actually bind these components with each other. I'll soon be writing small series about roadmap, challanges, alternatives and try - fails I've encountered during development in [Medium](https://medium.com/@aksoymelihcan). diff --git a/app/build.gradle b/app/build.gradle index be9186c..eac4a55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" android { defaultConfig { @@ -26,7 +25,6 @@ dependencies { implementation project(':features:launches') implementation project(':features:detail') - implementation libraries.coroutines implementation libraries.navigation debugImplementation libraries.leakCanary @@ -37,6 +35,7 @@ dependencies { compileOnly libraries.retrofit compileOnly libraries.room compileOnly libraries.paging + compileOnly libraries.swipeRefreshLayout // Need for proper renders in xml previews compileOnly libraries.constraintLayout diff --git a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt index 06b7c17..696b4dd 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/MainActivity.kt @@ -1,24 +1,22 @@ package com.melih.rocketscience import android.os.Bundle -import androidx.databinding.DataBindingUtil +import androidx.appcompat.widget.Toolbar import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.ui.NavigationUI -import com.melih.rocketscience.databinding.MainActivityBinding import dagger.android.support.DaggerAppCompatActivity class MainActivity : DaggerAppCompatActivity() { - private lateinit var binding: MainActivityBinding private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + setContentView(R.layout.activity_main) navController = findNavController(R.id.nav_host_fragment) - NavigationUI.setupWithNavController(binding.toolbar, navController) + NavigationUI.setupWithNavController(findViewById(R.id.toolbar), navController) } override fun onSupportNavigateUp(): Boolean { diff --git a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt index aa77983..ebbc8a3 100644 --- a/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt +++ b/app/src/main/kotlin/com/melih/rocketscience/di/AppComponent.kt @@ -8,8 +8,10 @@ import dagger.android.AndroidInjector @AppScope @Component( - modules = [AndroidInjectionModule::class, - AppModule::class], + modules = [ + AndroidInjectionModule::class, + AppModule::class + ], dependencies = [CoreComponent::class] ) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8950ebb..51075f4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,27 +1,22 @@ - + - + - - - - - - - + + diff --git a/build.gradle b/build.gradle index f1303a2..232dd0a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.41' + ext.kotlin_version = '1.3.50' ext.nav_version = '2.2.0-alpha01' repositories { @@ -12,7 +12,7 @@ buildscript { classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" - classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.0.0" + classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/core/build.gradle b/core/build.gradle index adb92c2..96de2b7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'kotlin-kapt' apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" android { dataBinding { @@ -23,6 +22,7 @@ dependencies { implementation libraries.liveDataKTX implementation libraries.navigation implementation libraries.picasso + implementation libraries.material testImplementation testLibraries.jUnitApi testImplementation testLibraries.mockk diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt index 0ca715f..dab5a6f 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseDaggerFragment.kt @@ -14,7 +14,7 @@ import javax.inject.Inject * Parent of fragments which has injections. Aim is to seperate [BaseFragment] functionality for fragments which * won't need any injection. * - * Note that fragments that extends from [BaseDaggerFragment] should contribute android injector. + * Note that fragments that extends from [BaseDaggerFragment] should contribute their injector. * * This class provides [viewModelFactory] which serves as factory for view models * in the project. It's injected by map of view models that this app is serving. Check [ViewModelFactory] @@ -22,16 +22,16 @@ import javax.inject.Inject */ abstract class BaseDaggerFragment : BaseFragment(), HasAndroidInjector { - // region Properties + //region Properties @get:Inject internal var androidInjector: DispatchingAndroidInjector? = null @Inject lateinit var viewModelFactory: ViewModelFactory - // endregion + //endregion - // region Functions + //region Functions override fun onAttach(context: Context) { AndroidSupportInjection.inject(this) @@ -39,5 +39,5 @@ abstract class BaseDaggerFragment : BaseFragment(), HasA } override fun androidInjector(): AndroidInjector? = androidInjector - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt index 7f37c1c..53e1d0a 100644 --- a/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt +++ b/core/src/main/kotlin/com/melih/core/base/lifecycle/BaseFragment.kt @@ -20,12 +20,18 @@ import com.melih.repository.interactors.base.Reason */ abstract class BaseFragment : Fragment() { - // region Properties + //region Abstractions + + @LayoutRes + abstract fun getLayoutId(): Int + //endregion + + //region Properties protected lateinit var binding: T - // endregion + //endregion - // region Functions + //region Functions override fun onCreateView( inflater: LayoutInflater, @@ -46,8 +52,5 @@ abstract class BaseFragment : Fragment() { block() }.show() } - - @LayoutRes - abstract fun getLayoutId(): Int - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt index 47b6126..ff1ff69 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingDataSource.kt @@ -37,12 +37,12 @@ const val INITIAL_PAGE = 0 @UseExperimental(ExperimentalCoroutinesApi::class) abstract class BasePagingDataSource : PageKeyedDataSource() { - // region Abstractions + //region Abstractions abstract fun loadDataForPage(page: Int): Flow>> // Load next page(s) - // endregion + //endregion - // region Properties + //region Properties private val _stateData = MutableLiveData() private val _reasonData = MutableLiveData() @@ -59,9 +59,9 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { */ val reasonData: LiveData get() = _reasonData - // endregion + //endregion - // region Functions + //region Functions override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { // Looping through channel as we'll receive any state, error or data here @@ -136,5 +136,5 @@ abstract class BasePagingDataSource : PageKeyedDataSource() { coroutineScope.cancel() super.invalidate() } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt index e948600..1a51f1e 100644 --- a/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt +++ b/core/src/main/kotlin/com/melih/core/base/paging/BasePagingFactory.kt @@ -16,20 +16,20 @@ import androidx.paging.DataSource */ abstract class BasePagingFactory : DataSource.Factory() { - // region Abstractions + //region Abstractions abstract fun createSource(): BasePagingDataSource - // endregion + //endregion - // region Properties + //region Properties private val _currentSource = MutableLiveData>() val currentSource: LiveData> get() = _currentSource - // endregion + //endregion - // region Functions + //region Functions override fun create(): DataSource = createSource().apply { _currentSource.postValue(this) } @@ -38,5 +38,5 @@ abstract class BasePagingFactory : DataSource.Factory() { * by calling [BasePagingDataSource.invalidate] */ fun invalidateDataSource() = currentSource.value?.invalidate() - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt index cfe44bc..3c9cdb4 100644 --- a/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt +++ b/core/src/main/kotlin/com/melih/core/base/recycler/BasePagingListAdapter.kt @@ -15,6 +15,8 @@ abstract class BasePagingListAdapter( private val clickListener: (T) -> Unit ) : PagedListAdapter>(callback) { + //region Abstractions + /** * This method will be called to create view holder to obfuscate layout inflation creation / process * @@ -27,6 +29,9 @@ abstract class BasePagingListAdapter( parent: ViewGroup, viewType: Int ): BaseViewHolder + //endregion + + //region Functions /** * [createViewHolder] will provide holders, no need to override this @@ -51,6 +56,7 @@ abstract class BasePagingListAdapter( holder.bind(item) } } + //endregion } /** @@ -58,6 +64,8 @@ abstract class BasePagingListAdapter( */ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) { + //region Functions + /** * Items are delivered to [bind] via [BaseListAdapter.onBindViewHolder] * @@ -65,4 +73,5 @@ abstract class BaseViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHo * @param position position from adapter */ abstract fun bind(item: T) + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt index 57da05b..413a602 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BasePagingViewModel.kt @@ -1,7 +1,7 @@ package com.melih.core.base.viewmodel import androidx.lifecycle.LiveData -import androidx.lifecycle.Transformations +import androidx.lifecycle.Transformations.switchMap import androidx.lifecycle.ViewModel import androidx.paging.PagedList import androidx.paging.toLiveData @@ -20,19 +20,19 @@ import com.melih.repository.interactors.base.State */ abstract class BasePagingViewModel : ViewModel() { - // region Abstractions + //region Abstractions abstract val factory: BasePagingFactory abstract val config: PagedList.Config - // endregion + //endregion - // region Properties + //region Properties /** * Observe [stateData] to get notified of state of data */ val stateData: LiveData by lazy { - Transformations.switchMap(factory.currentSource) { + switchMap(factory.currentSource) { it.stateData } } @@ -41,7 +41,7 @@ abstract class BasePagingViewModel : ViewModel() { * Observe [errorData] to get notified if an error occurs */ val errorData: LiveData by lazy { - Transformations.switchMap(factory.currentSource) { + switchMap(factory.currentSource) { it.reasonData } } @@ -52,9 +52,9 @@ abstract class BasePagingViewModel : ViewModel() { val pagedList: LiveData> by lazy { factory.toLiveData(config) } - // endregion + //endregion - // region Functions + //region Functions fun refresh() { factory.currentSource.value?.invalidate() @@ -66,5 +66,5 @@ abstract class BasePagingViewModel : ViewModel() { fun retry() { factory.currentSource.value?.invalidate() } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt index 2e3246b..6e7c440 100644 --- a/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt +++ b/core/src/main/kotlin/com/melih/core/base/viewmodel/BaseViewModel.kt @@ -15,10 +15,10 @@ import kotlinx.coroutines.launch */ abstract class BaseViewModel : ViewModel() { - // region Abstractions + //region Abstractions abstract suspend fun loadData() - // endregion + //endregion init { viewModelScope.launch { @@ -26,7 +26,7 @@ abstract class BaseViewModel : ViewModel() { } } - // region Properties + //region Properties private val _successData = MutableLiveData() private val _stateData = MutableLiveData() @@ -49,9 +49,9 @@ abstract class BaseViewModel : ViewModel() { */ val errorData: LiveData get() = _errorData - // endregion + //endregion - // region Functions + //region Functions /** * Default success handler which assigns given [data] to [successData] @@ -97,5 +97,5 @@ abstract class BaseViewModel : ViewModel() { loadData() } } - // endregion + //endregion } diff --git a/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt new file mode 100644 index 0000000..55bca7a --- /dev/null +++ b/core/src/main/kotlin/com/melih/core/extensions/DiffUtilHelper.kt @@ -0,0 +1,27 @@ +package com.melih.core.extensions + +import androidx.recyclerview.widget.DiffUtil + +/** + * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker. + * It uses [itemCheck] for both [DiffUtil.ItemCallback.areItemsTheSame] and [DiffUtil.ItemCallback.areContentsTheSame]. + */ +inline fun createDiffCallback(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = createDiffCallback(itemCheck, itemCheck) + +/** + * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker + */ +inline fun createDiffCallback( + crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean, + crossinline contentCheck: (oldItem: T, newItem: T) -> Boolean +) = object : DiffUtil.ItemCallback() { + + //region Functions + + override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = + itemCheck(oldItem, newItem) + + override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = + contentCheck(oldItem, newItem) + //endregion +} diff --git a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt index bf31e90..1396b42 100644 --- a/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt +++ b/core/src/main/kotlin/com/melih/core/extensions/LifecycleExtensions.kt @@ -3,9 +3,6 @@ package com.melih.core.extensions import androidx.fragment.app.Fragment import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders /** * Reduces required boilerplate code to observe a live data @@ -16,13 +13,3 @@ import androidx.lifecycle.ViewModelProviders fun Fragment.observe(data: LiveData, block: (T) -> Unit) { data.observe(this, Observer(block)) } - -/** - * Method for getting viewModel from factory and run a block over it if required for easy access - * - * crossinline for unwanted returns - */ -inline fun ViewModelProvider.Factory.createFor( - fragment: Fragment, - crossinline block: T.() -> Unit = {} -): T = ViewModelProviders.of(fragment, this)[T::class.java].apply(block) diff --git a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt b/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt deleted file mode 100644 index 011fac9..0000000 --- a/core/src/main/kotlin/com/melih/core/extensions/RecyclerExtensions.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.melih.core.extensions - -import androidx.recyclerview.widget.DiffUtil - -/** - * Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker - */ -inline fun getDiffCallbackForType(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = - itemCheck(oldItem, newItem) - - override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = - itemCheck(oldItem, newItem) -} diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index 270aec4..da95615 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -1,7 +1,11 @@ - #008577 - #00574B - #D81B60 - #8F8F8F + #f9a825 + #ffd95a + #c17900 + #757575 + #a4a4a4 + #494949 + #000000 + #686868 diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index ab4df8e..7bf765c 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -1,5 +1,7 @@ 8dp + 16dp + 4dp 11dp \ No newline at end of file diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index f7ae6d6..1d6d73c 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -1,35 +1,40 @@ - - + + + @style/AppTheme.ToolbarStyle + @style/AppTheme.CardViewStyle + - + - - + - + + + + diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt index f97d26a..6a648c9 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/DetailContributor.kt @@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector @Module abstract class DetailContributor { - // region Contributes + //region Contributes @ContributesAndroidInjector( modules = [DetailFragmentModule::class] ) @DetailFragmentScope abstract fun detailFragment(): DetailFragment - // endregion + //endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt index f7cfc4d..c4a58a2 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/di/modules/DetailFragmentModule.kt @@ -15,13 +15,13 @@ import dagger.multibindings.IntoMap @Module abstract class DetailFragmentModule { - // region ViewModels + //region ViewModels @Binds @IntoMap @ViewModelKey(DetailViewModel::class) abstract fun detailViewModel(detailViewModel: DetailViewModel): ViewModel - // endregion + //endregion @Module companion object { diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt index b9a9ca5..0ee2299 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailFragment.kt @@ -3,21 +3,20 @@ package com.melih.detail.ui import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.view.View +import androidx.fragment.app.viewModels 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 class DetailFragment : BaseDaggerFragment() { - // region Properties + //region Properties - private val viewModel: DetailViewModel - get() = viewModelFactory.createFor(this) - // endregion + private val viewModel by viewModels { viewModelFactory } + //endregion - // region Functions + //region Functions override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -34,5 +33,5 @@ class DetailFragment : BaseDaggerFragment() { } override fun getLayoutId(): Int = R.layout.fragment_detail - // endregion + //endregion } diff --git a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt index 83c28dd..f63ee29 100644 --- a/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt +++ b/features/detail/src/main/kotlin/com/melih/detail/ui/DetailViewModel.kt @@ -1,6 +1,6 @@ package com.melih.detail.ui -import androidx.lifecycle.Transformations +import androidx.lifecycle.Transformations.map import com.melih.core.base.viewmodel.BaseViewModel import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.GetLaunchDetails @@ -13,13 +13,13 @@ class DetailViewModel @Inject constructor( private val getLaunchDetailsParams: GetLaunchDetails.Params ) : BaseViewModel() { - // region Properties + //region Properties - val rocketName = Transformations.map(successData) { + val rocketName = map(successData) { it.rocket.name } - val description = Transformations.map(successData) { + val description = map(successData) { if (it.missions.isEmpty()) { "" } else { @@ -27,12 +27,12 @@ class DetailViewModel @Inject constructor( } } - val imageUrl = Transformations.map(successData) { + val imageUrl = map(successData) { it.rocket.imageURL } - // endregion + //endregion - // region Functions + //region Functions /** * Triggering interactor in view model scope @@ -42,5 +42,5 @@ class DetailViewModel @Inject constructor( it.handle(::handleState, ::handleFailure, ::handleSuccess) } } - // endregion + //endregion } diff --git a/features/detail/src/main/res/layout/fragment_detail.xml b/features/detail/src/main/res/layout/fragment_detail.xml index abf4e90..dde2a8f 100644 --- a/features/detail/src/main/res/layout/fragment_detail.xml +++ b/features/detail/src/main/res/layout/fragment_detail.xml @@ -1,75 +1,69 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - - - - - + + diff --git a/features/launches/build.gradle b/features/launches/build.gradle index 1128480..740d414 100644 --- a/features/launches/build.gradle +++ b/features/launches/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation libraries.paging + implementation libraries.swipeRefreshLayout testImplementation testLibraries.coroutinesTest } diff --git a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt index 9b1d947..7de0829 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/LaunchesContributor.kt @@ -12,12 +12,12 @@ import dagger.android.ContributesAndroidInjector @Module abstract class LaunchesContributor { - // region Contributes + //region Contributes @ContributesAndroidInjector( modules = [LaunchesFragmentModule::class] ) @LaunchesFragmentScope abstract fun launchesFragment(): LaunchesFragment - // endregion + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt index eadf9b4..fc47acb 100644 --- a/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt +++ b/features/launches/src/main/kotlin/com/melih/list/di/modules/LaunchesFragmentModule.kt @@ -14,13 +14,13 @@ import dagger.multibindings.IntoMap @Module abstract class LaunchesFragmentModule { - // region ViewModels + //region ViewModels @Binds @IntoMap @ViewModelKey(LaunchesViewModel::class) abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel - // endregion + //endregion @Module companion object { diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt index 66a7ec6..c4e02e7 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/LaunchesFragment.kt @@ -2,29 +2,29 @@ package com.melih.list.ui import android.os.Bundle import android.view.View +import androidx.fragment.app.viewModels import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.melih.core.actions.openDetail 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.PersistenceEmpty import com.melih.repository.interactors.base.State class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.OnRefreshListener { - // region Properties + //region Properties - private val viewModel: LaunchesViewModel - get() = viewModelFactory.createFor(this) + private val viewModel by viewModels { viewModelFactory } private val launchesAdapter = LaunchesAdapter(::onItemSelected) - // endregion + //endregion - // region Functions + //region Lifecyle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -36,6 +36,23 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O observeDataChanges() } + override fun onResume() { + super.onResume() + + // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683 + binding.swipeRefreshLayout.isEnabled = true + } + + override fun onPause() { + super.onPause() + + // Workaround for SwipeRefreshLayout leak -> https://issuetracker.google.com/issues/136153683 + binding.swipeRefreshLayout.isEnabled = false + } + //endregion + + //region Functions + private fun observeDataChanges() { // Observing state to show loading @@ -45,8 +62,10 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O // Observing error to show toast with retry action observe(viewModel.errorData) { - showSnackbarWithAction(it) { - viewModel.retry() + if (it !is PersistenceEmpty) { + showSnackbarWithAction(it) { + viewModel.retry() + } } } @@ -64,5 +83,5 @@ class LaunchesFragment : BaseDaggerFragment(), SwipeRefreshLayout.O } override fun getLayoutId(): Int = R.layout.fragment_launches - // endregion + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt index c998b2c..d759333 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/adapters/LaunchesAdapter.kt @@ -4,25 +4,30 @@ import android.view.LayoutInflater import android.view.ViewGroup import com.melih.core.base.recycler.BasePagingListAdapter import com.melih.core.base.recycler.BaseViewHolder -import com.melih.core.extensions.getDiffCallbackForType +import com.melih.core.extensions.createDiffCallback import com.melih.list.databinding.LaunchRowBinding import com.melih.repository.entities.LaunchEntity class LaunchesAdapter(itemClickListener: (LaunchEntity) -> Unit) : BasePagingListAdapter( - getDiffCallbackForType { oldItem, newItem -> oldItem.id == newItem.id }, + createDiffCallback { oldItem, newItem -> oldItem.id == newItem.id }, itemClickListener ) { + + //region Functions + override fun createViewHolder( inflater: LayoutInflater, parent: ViewGroup, viewType: Int ): BaseViewHolder = LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false)) - + //endregion } class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder(binding) { + //region Functions + override fun bind(item: LaunchEntity) { binding.entity = item @@ -31,4 +36,5 @@ class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder binding.executePendingBindings() } + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt index 9ec0bf8..a234c44 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSource.kt @@ -8,6 +8,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import javax.inject.Inject +/** + * Uses [GetLaunches] to get data for pagination + */ class LaunchesPagingSource @Inject constructor( private val getLaunches: GetLaunches, private val getLaunchesParams: GetLaunches.Params diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt index d176076..f9f7c46 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/paging/LaunchesPagingSourceFactory.kt @@ -10,5 +10,8 @@ class LaunchesPagingSourceFactory @Inject constructor( private val sourceProvider: Provider ) : BasePagingFactory() { + //region Functions + override fun createSource(): BasePagingDataSource = sourceProvider.get() + //endregion } diff --git a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt index 4576d9c..fc45b4c 100644 --- a/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt +++ b/features/launches/src/main/kotlin/com/melih/list/ui/vm/LaunchesViewModel.kt @@ -12,34 +12,12 @@ class LaunchesViewModel @Inject constructor( private val launchesPagingConfig: PagedList.Config ) : BasePagingViewModel() { - // region Properties + //region Properties override val factory: BasePagingFactory get() = launchesPagingSourceFactory override val config: PagedList.Config get() = launchesPagingConfig - - //private val _filteredItems = MediatorLiveData>() - - //val filteredItems: LiveData> - // 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 - // } else { - // pagedList.value - // } - //} - // endregion + //endregion } diff --git a/features/launches/src/main/res/layout/fragment_launches.xml b/features/launches/src/main/res/layout/fragment_launches.xml index cac321d..dce7104 100644 --- a/features/launches/src/main/res/layout/fragment_launches.xml +++ b/features/launches/src/main/res/layout/fragment_launches.xml @@ -1,37 +1,36 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - - - + + + diff --git a/features/launches/src/main/res/layout/row_launch.xml b/features/launches/src/main/res/layout/row_launch.xml index bb62bbf..184d1c0 100644 --- a/features/launches/src/main/res/layout/row_launch.xml +++ b/features/launches/src/main/res/layout/row_launch.xml @@ -1,77 +1,69 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + - - + + - + - + - + - + - - - - + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 944f2c1..83937fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 13 10:50:25 CEST 2019 +#Wed Sep 04 15:39:59 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip diff --git a/repository/src/main/kotlin/com/melih/repository/Constants.kt b/repository/src/main/kotlin/com/melih/repository/Constants.kt index 05de778..5d5e2dc 100644 --- a/repository/src/main/kotlin/com/melih/repository/Constants.kt +++ b/repository/src/main/kotlin/com/melih/repository/Constants.kt @@ -1,5 +1,4 @@ package com.melih.repository -const val DEFAULT_NAME = "Default name" -const val EMPTY_STRING = "" -const val DEFAULT_IMAGE_SIZE = 480 +internal const val DEFAULT_NAME = "Default name" +internal const val EMPTY_STRING = "" diff --git a/repository/src/main/kotlin/com/melih/repository/Repository.kt b/repository/src/main/kotlin/com/melih/repository/Repository.kt index 82a9d41..af1e3d8 100644 --- a/repository/src/main/kotlin/com/melih/repository/Repository.kt +++ b/repository/src/main/kotlin/com/melih/repository/Repository.kt @@ -4,10 +4,13 @@ import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.base.Result /** - * Abstract class to create contract in sources to seperate low level business logic from build and return type + * Contract for sources to seperate low level business logic from build and return type */ abstract class Repository { + //region Abstractions + internal abstract suspend fun getNextLaunches(count: Int, page: Int): Result> internal abstract suspend fun getLaunchById(id: Long): Result + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt index 7bf13d5..5f9de0d 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/GetLaunchDetails.kt @@ -18,11 +18,16 @@ import javax.inject.Inject @UseExperimental(ExperimentalCoroutinesApi::class) class GetLaunchDetails @Inject constructor() : BaseInteractor() { + //region Properties + @field:Inject internal lateinit var networkSource: NetworkSource @field:Inject internal lateinit var persistenceSource: PersistenceSource + //endregion + + //region Functions override suspend fun FlowCollector>.run(params: Params) { val result = persistenceSource.getLaunchById(params.id) @@ -42,6 +47,7 @@ class GetLaunchDetails @Inject constructor() : BaseInteractor, GetLaunches.Params>() { + //region Properties + @field:Inject internal lateinit var networkSource: NetworkSource @field:Inject internal lateinit var persistenceSource: PersistenceSource + //endregion + + //region Functions override suspend fun FlowCollector>>.run(params: Params) { @@ -40,6 +45,7 @@ class GetLaunches @Inject constructor() : BaseInteractor, Get } } } + //endregion data class Params( val count: Int = DEFAULT_LAUNCHES_AMOUNT, diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt index e431a2d..bb7605c 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/BaseInteractor.kt @@ -13,12 +13,12 @@ import kotlinx.coroutines.flow.flowOn @UseExperimental(ExperimentalCoroutinesApi::class) abstract class BaseInteractor { - // region Abstractions + //region Abstractions protected abstract suspend fun FlowCollector>.run(params: P) - // endregion + //endregion - // region Functions + //region Functions operator fun invoke(params: P) = flow> { @@ -26,7 +26,7 @@ abstract class BaseInteractor { run(params) emit(State.Loaded()) }.flowOn(Dispatchers.IO) - // endregion + //endregion } /** diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt index e14b96e..fa14e9a 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Reason.kt @@ -9,6 +9,8 @@ import com.melih.repository.R */ sealed class Reason(@StringRes val messageRes: Int) +//region Subclasses + class NetworkError : Reason(R.string.reason_network) class EmptyResultError : Reason(R.string.reason_empty_body) class GenericError : Reason(R.string.reason_generic) @@ -16,3 +18,4 @@ class ResponseError : Reason(R.string.reason_response) class TimeoutError : Reason(R.string.reason_timeout) class PersistenceEmpty : Reason(R.string.reason_persistance_empty) class NoNetworkPersistenceEmpty : Reason(R.string.reason_no_network_persistance_empty) +//endregion diff --git a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt index a5684c7..e34eea7 100644 --- a/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt +++ b/repository/src/main/kotlin/com/melih/repository/interactors/base/Result.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi @UseExperimental(ExperimentalCoroutinesApi::class) sealed class Result -// region Subclasses +//region Subclasses class Success(val successData: T) : Result() class Failure(val errorData: Reason) : Result() @@ -18,9 +18,9 @@ sealed class State : Result() { class Loading : State() class Loaded : State() } -// endregion +//endregion -// region Extensions +//region Extensions inline fun Result.handle(stateBlock: (State) -> Unit, failureBlock: (Reason) -> Unit, successBlock: (T) -> Unit) { when (this) { @@ -50,4 +50,4 @@ inline fun Result.onState(stateBlock: (State) -> Unit): Result { return this } -// endregion +//endregion diff --git a/repository/src/main/kotlin/com/melih/repository/network/Api.kt b/repository/src/main/kotlin/com/melih/repository/network/Api.kt index 0b410d8..2783559 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/Api.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/Api.kt @@ -12,6 +12,8 @@ import retrofit2.http.Query */ internal interface Api { + //region Get + @GET("launch/next/{count}") suspend fun getNextLaunches( @Path("count") count: Int, @@ -22,4 +24,5 @@ internal interface Api { suspend fun getLaunchById( @Path("id") id: Long ): Response + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt index 85ca556..c56ffb8 100644 --- a/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt +++ b/repository/src/main/kotlin/com/melih/repository/network/ApiImpl.kt @@ -16,7 +16,7 @@ internal const val TIMEOUT_DURATION = 7L internal class ApiImpl @Inject constructor() : Api { - // region Properties + //region Properties private val service by lazy { val moshi = Moshi.Builder() @@ -39,7 +39,9 @@ internal class ApiImpl @Inject constructor() : Api { .build() .create(Api::class.java) } - // endregion + //endregion + + //region Functions override suspend fun getNextLaunches( count: Int, @@ -51,4 +53,5 @@ internal class ApiImpl @Inject constructor() : Api { id: Long ): Response = service.getLaunchById(id) + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt index 4ab8fb7..636dbab 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/LaunchesDatabase.kt @@ -28,6 +28,8 @@ const val DB_NAME = "LaunchesDB" ) internal abstract class LaunchesDatabase : RoomDatabase() { + //region Companion + companion object { private lateinit var instance: LaunchesDatabase @@ -42,6 +44,10 @@ internal abstract class LaunchesDatabase : RoomDatabase() { } } + //endregion + + //region Abstractions internal abstract val launchesDao: LaunchesDao + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt index 735d615..cc9d698 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseConverter.kt @@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory */ abstract class BaseConverter { + //region Abstractions + + abstract fun getAdapter(moshi: Moshi): JsonAdapter + //endregion + + //region Properties + private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() + //endregion - abstract fun getAdapter(moshi: Moshi): JsonAdapter - - // region Functions + //region Functions @TypeConverter fun convertFrom(item: T) = @@ -25,5 +31,5 @@ abstract class BaseConverter { @TypeConverter fun convertTo(string: String) = getAdapter(moshi).fromJson(string) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt index f304c0a..7bdc189 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/BaseListConverter.kt @@ -10,13 +10,19 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory */ abstract class BaseListConverter { + //region Abstractions + + abstract fun getAdapter(moshi: Moshi): JsonAdapter> + //endregion + + //region Properties + private val moshi = Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() + //endregion - abstract fun getAdapter(moshi: Moshi): JsonAdapter> - - // region Functions + //region Functions @TypeConverter fun convertFrom(items: List) = @@ -25,5 +31,5 @@ abstract class BaseListConverter { @TypeConverter fun convertTo(string: String): List? = getAdapter(moshi).fromJson(string) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt index 6742ecb..399efb9 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/LocationConverter.kt @@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi * Converts [location][LocationEntity] */ class LocationConverter : BaseConverter() { + override fun getAdapter(moshi: Moshi): JsonAdapter = LocationEntityJsonAdapter(moshi) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt index de2e3b8..66b3606 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/converters/RocketConverter.kt @@ -9,6 +9,7 @@ import com.squareup.moshi.Moshi * Converts [rocket][RocketEntity] */ class RocketConverter : BaseConverter() { + override fun getAdapter(moshi: Moshi): JsonAdapter = - RocketEntityJsonAdapter(moshi) + RocketEntityJsonAdapter(moshi) } diff --git a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt index df4b014..2490cdd 100644 --- a/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt +++ b/repository/src/main/kotlin/com/melih/repository/persistence/dao/LaunchesDao.kt @@ -12,7 +12,7 @@ import com.melih.repository.entities.LaunchEntity @Dao internal abstract class LaunchesDao { - // region Queries + //region Queries @Query("SELECT * FROM Launches ORDER BY launchStartTime DESC LIMIT :count OFFSET :page*:count") abstract suspend fun getLaunches(count: Int, page: Int): List @@ -22,14 +22,14 @@ internal abstract class LaunchesDao { @Query("DELETE FROM Launches") abstract suspend fun nukeLaunches() - // endregion + //endregion - // region Insertion + //region Insertion @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunches(launches: List) @Insert(onConflict = OnConflictStrategy.REPLACE) abstract suspend fun saveLaunch(launch: LaunchEntity) - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt index ed14fbe..785e0a2 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/NetworkSource.kt @@ -1,7 +1,6 @@ package com.melih.repository.sources import android.net.NetworkInfo -import com.melih.repository.DEFAULT_IMAGE_SIZE import com.melih.repository.Repository import com.melih.repository.entities.LaunchEntity import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT @@ -19,6 +18,8 @@ import java.io.IOException import javax.inject.Inject import javax.inject.Provider +private const val DEFAULT_IMAGE_SIZE = 480 + /** * NetworkSource for fetching results using api and wrapping them as contracted in [repository][Repository], * returning either [failure][Failure] with proper [reason][Reason] or [success][Success] with data @@ -27,16 +28,17 @@ internal class NetworkSource @Inject constructor( private val apiImpl: ApiImpl, private val networkInfoProvider: Provider ) : Repository() { - // region Properties + + //region Properties private val isNetworkConnected: Boolean get() { val networkInfo = networkInfoProvider.get() return networkInfo != null && networkInfo.isConnected } - // endregion + //endregion - // region Functions + //region Functions override suspend fun getNextLaunches(count: Int, page: Int): Result> = safeExecute({ @@ -73,8 +75,8 @@ internal class NetworkSource @Inject constructor( } } - private suspend inline fun safeExecute( - block: suspend () -> Response, + private inline fun safeExecute( + block: () -> Response, transform: (T) -> R ) = if (isNetworkConnected) { @@ -112,5 +114,5 @@ internal class NetworkSource @Inject constructor( } catch (e: Exception) { imageUrl } - // endregion + //endregion } diff --git a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt index 366c683..c49881d 100644 --- a/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt +++ b/repository/src/main/kotlin/com/melih/repository/sources/PersistenceSource.kt @@ -16,7 +16,8 @@ import javax.inject.Inject internal class PersistenceSource @Inject constructor( ctx: Context ) : Repository() { - // region Functions + + //region Functions private val launchesDatabase = LaunchesDatabase.getInstance(ctx) @@ -45,5 +46,5 @@ internal class PersistenceSource @Inject constructor( internal suspend fun saveLaunch(launch: LaunchEntity) { launchesDatabase.launchesDao.saveLaunch(launch) } - // endregion + //endregion } diff --git a/scripts/default_android_config.gradle b/scripts/default_android_config.gradle index 576744b..4546de2 100644 --- a/scripts/default_android_config.gradle +++ b/scripts/default_android_config.gradle @@ -2,6 +2,7 @@ apply from: "$rootProject.projectDir/scripts/default_dependencies.gradle" android { compileSdkVersion versions.compileSdkVersion + buildToolsVersion versions.buildToolsVersion defaultConfig { minSdkVersion versions.minSdkVersion diff --git a/scripts/dependencies.gradle b/scripts/dependencies.gradle index 2c3811a..21ff897 100644 --- a/scripts/dependencies.gradle +++ b/scripts/dependencies.gradle @@ -1,77 +1,80 @@ ext { versions = [ - minSdkVersion : 21, - compileSdkVersion : 28, - targetSdkVersion : 28, - buildToolsVersion : "28.0.3", - supportLibraryVersion : "28.0.0", - appCompatVersion : "1.1.0-rc01", - lifecycleVersion : "2.2.0-alpha03", - fragmentVersion : "1.2.0-alpha02", - workManagerVersion : "2.2.0-rc01", - constraintLayoutVesion: "2.0.0-beta1", - cardViewVersion : "1.0.0", - recyclerViewVersion : "1.1.0-beta02", - pagingVersion : "2.1.0", - viewPagerVersion : "1.0.0-beta03", - collectionVersion : "1.1.0", - roomVersion : "2.2.0-alpha02", - daggerVersion : "2.24", - okHttpVersion : "4.0.1", - retrofitVersion : "2.6.1", - picassoVersion : "2.71828", - moshiVersion : "1.8.0", - coroutinesVersion : "1.3.0-RC2", - leakCanaryVersion : "2.0-beta-2", - timberVersion : "4.7.1", - jUnitVersion : "5.5.1", - espressoVersion : "3.2.0", - mockkVersion : "1.9.3", - kluentVersion : "1.53", + minSdkVersion : 21, + compileSdkVersion : 29, + targetSdkVersion : 29, + buildToolsVersion : "29.0.2", + appCompatVersion : "1.1.0-rc01", + lifecycleVersion : "2.2.0-alpha03", + fragmentVersion : "1.2.0-alpha02", + workManagerVersion : "2.3.0-alpha01", + constraintLayoutVesion : "2.0.0-beta2", + cardViewVersion : "1.0.0", + recyclerViewVersion : "1.1.0-beta03", + pagingVersion : "2.1.0", + viewPagerVersion : "1.0.0-beta03", + materialVersion : "1.1.0-alpha09", + swipeRefreshLayoutVersion: "1.1.0-alpha02", + collectionVersion : "1.1.0", + roomVersion : "2.2.0-rc01", + daggerVersion : "2.24", + okHttpVersion : "4.0.1", + retrofitVersion : "2.6.1", + picassoVersion : "2.71828", + moshiVersion : "1.8.0", + coroutinesVersion : "1.3.0-RC2", + leakCanaryVersion : "2.0-beta-2", + timberVersion : "4.7.1", + jUnitVersion : "5.5.1", + espressoVersion : "3.2.0", + mockkVersion : "1.9.3", + kluentVersion : "1.53", ] libraries = [ /** * Android libraries */ - appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}", - recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}", - cardView : "androidx.cardview:cardview:${versions.cardViewVersion}", - constraintLayout: "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}", - multixDex : "androidx.multidex:multidex:2.0.1", - fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}", + appCompat : "androidx.appcompat:appcompat:${versions.appCompatVersion}", + recyclerView : "androidx.recyclerview:recyclerview:${versions.recyclerViewVersion}", + cardView : "androidx.cardview:cardview:${versions.cardViewVersion}", + constraintLayout : "androidx.constraintlayout:constraintlayout:${versions.constraintLayoutVesion}", + multixDex : "androidx.multidex:multidex:2.0.1", + fragment : "androidx.fragment:fragment-ktx:${versions.fragmentVersion}", + material : "com.google.android.material:material:${versions.materialVersion}", /** * Jetpack */ - navigation : [ + navigation : [ "androidx.navigation:navigation-fragment-ktx:$nav_version", "androidx.navigation:navigation-ui-ktx:$nav_version" ], - room : [ + room : [ "androidx.room:room-runtime:${versions.roomVersion}", "androidx.room:room-ktx:${versions.roomVersion}" ], - lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}", - liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}", - workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}", - paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}", - viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}", - collection : "androidx.collection:collection-ktx:${versions.collectionVersion}", + lifecycle : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycleVersion}", + liveDataKTX : "androidx.lifecycle:lifecycle-livedata-ktx:${versions.lifecycleVersion}", + workManager : "androidx.work:work-runtime-ktx:${versions.workManagerVersion}", + paging : "androidx.paging:paging-runtime-ktx:${versions.pagingVersion}", + viewPager : "androidx.viewpager2:viewpager2:${versions.viewPagerVersion}", + collection : "androidx.collection:collection-ktx:${versions.collectionVersion}", + swipeRefreshLayout: "androidx.swiperefreshlayout:swiperefreshlayout:${versions.swipeRefreshLayoutVersion}", /** * Kotlin */ - kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", - coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}", + kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", + coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutinesVersion}", /** * Dagger */ - dagger : [ + dagger : [ "com.google.dagger:dagger:${versions.daggerVersion}", "com.google.dagger:dagger-android:${versions.daggerVersion}", "com.google.dagger:dagger-android-support:${versions.daggerVersion}" @@ -80,17 +83,17 @@ ext { /** * OkHttp */ - okHttp : [ + okHttp : [ "com.squareup.okhttp3:okhttp:${versions.okHttpVersion}", "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}" ], - okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}", + okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okHttpVersion}", /** * Retrofit */ - retrofit : [ + retrofit : [ "com.squareup.retrofit2:retrofit:${versions.retrofitVersion}", "com.squareup.retrofit2:converter-moshi:${versions.retrofitVersion}" ], @@ -98,27 +101,27 @@ ext { /** * Moshi */ - moshi : [ + moshi : [ "com.squareup.moshi:moshi:${versions.moshiVersion}", "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}" ], - moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}", + moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshiVersion}", /** * Picasso for image loading */ - picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}", + picasso : "com.squareup.picasso:picasso:${versions.picassoVersion}", /** * LeakCanary */ - leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}", + leakCanary : "com.squareup.leakcanary:leakcanary-android:${versions.leakCanaryVersion}", /** * Timber */ - timber : "com.jakewharton.timber:timber:${versions.timberVersion}" + timber : "com.jakewharton.timber:timber:${versions.timberVersion}" ] annotationProcessors = [ diff --git a/scripts/flavors.gradle b/scripts/flavors.gradle deleted file mode 100644 index 496ab22..0000000 --- a/scripts/flavors.gradle +++ /dev/null @@ -1,18 +0,0 @@ -android { - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - testCoverageEnabled = true - } - - dev { - initWith debug - } - } -} diff --git a/scripts/module.gradle b/scripts/module.gradle index 8bfa550..de40f3f 100644 --- a/scripts/module.gradle +++ b/scripts/module.gradle @@ -1,3 +1,2 @@ apply from: "$rootProject.projectDir/scripts/default_android_config.gradle" apply from: "$rootProject.projectDir/scripts/sources.gradle" -apply from: "$rootProject.projectDir/scripts/flavors.gradle" diff --git a/scripts/sources.gradle b/scripts/sources.gradle index dcbd8fc..a6af0a8 100644 --- a/scripts/sources.gradle +++ b/scripts/sources.gradle @@ -14,4 +14,21 @@ android { jvmTarget = '1.8' freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental" } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + testCoverageEnabled = true + } + + dev { + initWith debug + } + } }