Skip to content

Commit 5211e27

Browse files
Merge branch 'release/v1.0.0-alpha01'
2 parents 0a406c4 + 94d420f commit 5211e27

62 files changed

Lines changed: 2105 additions & 428 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# MVVM Clean Architecture with RxJava3+Coroutines Flow, Static Code Analysis, Dagger Hilt, Dynamic Features
2+
3+
[![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/)
4+
[![Kotlin Version](https://img.shields.io/badge/kotlin-1.4.0-blue.svg)](https://kotlinlang.org)
5+
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
6+
[![codecov](https://codecov.io/gh/andremion/Theatre/graph/badge.svg)](https://codecov.io/gh/andremion/Theatre)
7+
8+
9+
## About
10+
11+
Sample project that build with MVVM clean architure and various cool techs including RxJava3 and Coroutines Flow, Dynamic Feature modules as base of BottomNavigationView or ViewPager2, with both OfflineFirst and OfflineLast approaches as database Single Source of Truth and TDD.
12+
13+
Unit tests are written with JUnit4, JUnit5, MockK, Truth, MockWebServer.
14+
15+
| Flow | RxJava3 | Pagination | Favorites
16+
| ------------------|-------------| -----|--------------|
17+
| <img src="./screenshots/property_flow.png"/> | <img src="./screenshots/property_rxjava3.png"/> | <img src="./screenshots/property_pagination.png"/> |<img src="./screenshots/favorites.png"/> |
18+
19+
20+
## Overview
21+
* Gradle Kotlin DSL is used for setting up gradle files with ```buildSrc``` folder and extensions.
22+
* KtLint, Detekt, and Git Hooks is used for checking, and formatting code and validating code before commits.
23+
* Dagger Hilt, Dynamic Feature Modules with Navigation Components, ViewModel, Retrofit, Room, RxJava, Coroutines libraries adn dependencies are set up.
24+
* ```features``` and ```libraries``` folders are used to include android libraries and dynamic feature modules
25+
* In core module dagger hilt dependencies and ```@EntryPoint``` is created
26+
* Data module uses Retrofit and Room to provide Local and Remote data sources
27+
* Repository provides offline and remote fetch function with mapping and local save, delete and fetch functions
28+
* Domain module uses useCase classes to implment business logic to fetch and forward data
29+
* ViewModel uses LiveData with data-binding to display LOADING, and ERROR or SUCCESS states.
30+
31+
## Built With 🛠
32+
33+
Some of the popular libraries and MVVM clean architecture used with offline-first and offline-last with Room database and Retrofit as data source
34+
35+
* [Kotlin](https://kotlinlang.org/) - First class and official programming language for Android development.
36+
37+
* [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) - Threads on steroids for Kotlin
38+
* [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) - A cold asynchronous data stream that sequentially emits values and completes normally or with an exception.
39+
* [RxJava3](https://github.com/ReactiveX/RxJava) - Newest version of famous reactive programming library for Java, and other languages
40+
* [Android JetPack](https://developer.android.com/jetpack) - Collection of libraries that help you design robust, testable, and maintainable apps.
41+
* [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - Data objects that notify views when the underlying database changes.
42+
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - Stores UI-related data that isn't destroyed on UI changes.
43+
* [DataBinding](https://developer.android.com/topic/libraries/data-binding) - Generates a binding class for each XML layout file present in that module and allows you to more easily write code that interacts with views.
44+
* [Navigation Components](https://developer.android.com/guide/navigation/navigation-getting-started) Navigate fragments as never easier before
45+
* [Dynamic Feature Modules](https://developer.android.com/guide/playcore/dynamic-delivery) Dynamic modules for adding or removing based on preference
46+
* [Material Components for Android](https://github.com/material-components/material-components-android) - Modular and customizable Material Design UI components for Android.
47+
* [Dependency Injection](https://developer.android.com/training/dependency-injection) -
48+
* [Hilt-Dagger](https://dagger.dev/hilt/) - Standard library to incorporate Dagger dependency injection into an Android application.
49+
* [Hilt-ViewModel](https://developer.android.com/training/dependency-injection/hilt-jetpack) - DI for injecting `ViewModel`.
50+
* [Retrofit](https://square.github.io/retrofit/) - A type-safe HTTP client for Android and Java.
51+
* [Glide](https://github.com/bumptech/glide) - Image loading library.
52+
* [Lottie](http://airbnb.io/lottie) - animation library
53+
54+
* Architecture
55+
* Clean Architecture
56+
* MVVM + MVI
57+
* Offline first/last with Room an Retrofit
58+
* [Dynamic feature modules](https://developer.android.com/studio/projects/dynamic-delivery)
59+
* Tests
60+
* [Unit Tests](https://en.wikipedia.org/wiki/Unit_testing) ([JUnit5](https://junit.org/junit5/)) ([JUnit4](https://junit.org/junit4/))
61+
* [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver) Mock server for testing Api requests with OkHttp and Retrofit
62+
* [Mockk](https://mockk.io/) Mockking library for Kotlin
63+
* [Truth](https://truth.dev) Assertion library
64+
* Gradle
65+
* [Gradle Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
66+
* Custom tasks
67+
* Plugins ([Ktlint](https://github.com/JLLeitschuh/ktlint-gradle), [Detekt](https://github.com/arturbosch/detekt#with-gradle), [SafeArgs](https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args)), [Git Hooks](https://githooks.com)
68+
69+
70+
### Modularaization, Library and Feature Modules
71+
72+
Uses both library modules and dynamic feature modules
73+
74+
## Architecture
75+
76+
Uses concepts of clean architecture
77+
78+

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<resources>
2-
<string name="app_name">PropertyFindAR</string>
2+
<string name="app_name">Property FindAR</string>
33
<string name="title_home">Home</string>
44
<string name="title_favorites">Favorites</string>
55
<string name="title_notification">Notification</string>

features/account/src/main/res/layout/fragment_account.xml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,20 @@
1313
android:text="ACCOUNT\n Under Construction"
1414
android:textSize="30dp"
1515
android:textStyle="bold"
16-
app:layout_constraintBottom_toBottomOf="parent"
16+
app:layout_constraintBottom_toTopOf="@+id/lavUnderConstruction"
1717
app:layout_constraintEnd_toEndOf="parent"
1818
app:layout_constraintStart_toStartOf="parent"
19-
app:layout_constraintTop_toTopOf="parent"
20-
app:layout_constraintVertical_bias="0.25" />
19+
app:layout_constraintTop_toTopOf="parent" />
2120

2221
<com.airbnb.lottie.LottieAnimationView
2322
android:id="@+id/lavUnderConstruction"
2423
android:layout_width="0dp"
2524
android:layout_height="0dp"
26-
android:layout_marginTop="8dp"
2725
app:layout_constraintBottom_toBottomOf="parent"
2826
app:layout_constraintDimensionRatio="1:1"
2927
app:layout_constraintEnd_toEndOf="parent"
3028
app:layout_constraintStart_toStartOf="parent"
31-
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
32-
app:layout_constraintVertical_bias="0.4"
29+
app:layout_constraintTop_toTopOf="parent"
3330
app:layout_constraintWidth_percent=".6"
3431
app:lottie_autoPlay="true"
3532
app:lottie_fileName="construction_process.json" />

features/favorites/src/main/res/layout/fragment_favorites.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<layout xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:app="http://schemas.android.com/apk/res-auto">
4+
45
<androidx.constraintlayout.widget.ConstraintLayout
56
android:layout_width="match_parent"
67
android:layout_height="match_parent">
@@ -14,22 +15,20 @@
1415
android:textSize="30dp"
1516
android:textStyle="bold"
1617
app:layout_constraintBottom_toBottomOf="parent"
18+
app:layout_constraintBottom_toTopOf="@+id/lavUnderConstruction"
1719
app:layout_constraintEnd_toEndOf="parent"
1820
app:layout_constraintStart_toStartOf="parent"
19-
app:layout_constraintTop_toTopOf="parent"
20-
app:layout_constraintVertical_bias="0.25" />
21+
app:layout_constraintTop_toTopOf="parent" />
2122

2223
<com.airbnb.lottie.LottieAnimationView
2324
android:id="@+id/lavUnderConstruction"
2425
android:layout_width="0dp"
2526
android:layout_height="0dp"
26-
android:layout_marginTop="8dp"
2727
app:layout_constraintBottom_toBottomOf="parent"
2828
app:layout_constraintDimensionRatio="1:1"
2929
app:layout_constraintEnd_toEndOf="parent"
3030
app:layout_constraintStart_toStartOf="parent"
31-
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
32-
app:layout_constraintVertical_bias="0.4"
31+
app:layout_constraintTop_toTopOf="parent"
3332
app:layout_constraintWidth_percent=".6"
3433
app:lottie_autoPlay="true"
3534
app:lottie_fileName="construction_process.json" />

features/home/src/main/java/com/smarttoolfactory/home/adapter/HomeViewPager2FragmentStateAdapter.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@ class HomeViewPager2FragmentStateAdapter(fragmentManager: FragmentManager, lifec
2323
override fun createFragment(position: Int): Fragment {
2424

2525
return when (position) {
26-
27-
// Fragment with Flow
2826
0 -> NavHostContainerFragment.createNavHostContainerFragment(
2927
R.layout.fragment_navhost_property_list_flow,
3028
R.id.nested_nav_host_fragment_property_list
3129
)
3230

33-
// Fragment with Pagination
34-
else -> NavHostContainerFragment.createNavHostContainerFragment(
31+
// Fragment with RxJava3
32+
1 -> NavHostContainerFragment.createNavHostContainerFragment(
3533
R.layout.fragment_navhost_property_list_rxjava3,
3634
R.id.nested_nav_host_fragment_property_list
3735
)
36+
37+
// Fragment with Flow + Pagination
38+
else -> NavHostContainerFragment.createNavHostContainerFragment(
39+
R.layout.fragment_navhost_property_list_paged,
40+
R.id.nested_nav_host_fragment_property_list
41+
)
3842
}
3943
}
4044
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.smarttoolfactory.home.adapter
2+
3+
class LoadingAdapter

features/home/src/main/java/com/smarttoolfactory/home/adapter/PropertyListAdapter.kt

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.smarttoolfactory.home.adapter
22

3+
import android.graphics.Color
34
import android.widget.ImageButton
45
import androidx.annotation.LayoutRes
56
import androidx.databinding.ViewDataBinding
@@ -57,12 +58,15 @@ class PropertyItemListAdapter(
5758
onLikeButtonClick(this)
5859

5960
// Set image source of like button
61+
val likeImageButton = (likeButton as? ImageButton)
6062
val imageResource = if (isFavorite) {
61-
R.drawable.ic_baseline_favorite_24
63+
likeImageButton?.setColorFilter(Color.rgb(244, 81, 30))
64+
R.drawable.ic_baseline_favorite_30
6265
} else {
63-
R.drawable.ic_baseline_favorite_border_24
66+
likeImageButton?.setColorFilter(Color.rgb(41, 182, 246))
67+
R.drawable.ic_baseline_favorite_border_30
6468
}
65-
(likeButton as? ImageButton)?.setImageResource(imageResource)
69+
likeImageButton?.setImageResource(imageResource)
6670
}
6771
}
6872
}
@@ -78,17 +82,11 @@ class PropertyItemListAdapter(
7882
*/
7983
class PropertyItemDiffCallback : DiffUtil.ItemCallback<PropertyItem>() {
8084

81-
override fun areItemsTheSame(
82-
oldItem: PropertyItem,
83-
newItem: PropertyItem
84-
): Boolean {
85-
return oldItem === newItem
85+
override fun areItemsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
86+
return oldItem.id == newItem.id
8687
}
8788

88-
override fun areContentsTheSame(
89-
oldItem: PropertyItem,
90-
newItem: PropertyItem
91-
): Boolean {
92-
return oldItem.id == newItem.id
89+
override fun areContentsTheSame(oldItem: PropertyItem, newItem: PropertyItem): Boolean {
90+
return oldItem == newItem
9391
}
9492
}

features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.smarttoolfactory.home.di
22

33
import androidx.fragment.app.Fragment
44
import com.smarttoolfactory.core.di.CoreModuleDependencies
5-
import com.smarttoolfactory.home.propertylist.PropertyListFlowFragment
6-
import com.smarttoolfactory.home.propertylist.PropertyListRxjava3Fragment
5+
import com.smarttoolfactory.home.propertylist.flow.PropertyListFlowFragment
6+
import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListFragment
7+
import com.smarttoolfactory.home.propertylist.rxjava.PropertyListRxjava3Fragment
78
import dagger.BindsInstance
89
import dagger.Component
910

@@ -15,6 +16,7 @@ interface HomeComponent {
1516

1617
fun inject(fragment: PropertyListFlowFragment)
1718
fun inject(fragment: PropertyListRxjava3Fragment)
19+
fun inject(fragment: PagedPropertyListFragment)
1820

1921
@Component.Factory
2022
interface Factory {

features/home/src/main/java/com/smarttoolfactory/home/di/HomeModule.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package com.smarttoolfactory.home.di
22

33
import androidx.fragment.app.Fragment
44
import androidx.lifecycle.ViewModelProvider
5+
import com.smarttoolfactory.home.propertylist.flow.PropertyListViewModelFlow
6+
import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListViewModel
7+
import com.smarttoolfactory.home.propertylist.rxjava.PropertyListViewModelRxJava3
8+
import com.smarttoolfactory.home.viewmodel.PagedPropertyListViewModelFactory
59
import com.smarttoolfactory.home.viewmodel.PropertyListFlowViewModelFactory
610
import com.smarttoolfactory.home.viewmodel.PropertyListRxJava3ViewModelFactory
7-
import com.smarttoolfactory.home.viewmodel.PropertyListViewModelFlow
8-
import com.smarttoolfactory.home.viewmodel.PropertyListViewModelRxJava3
911
import dagger.Module
1012
import dagger.Provides
1113
import dagger.hilt.InstallIn
@@ -18,13 +20,29 @@ import kotlinx.coroutines.SupervisorJob
1820
@Module
1921
class HomeModule {
2022

23+
/**
24+
* Property ViewModel that uses Flow for data operation
25+
*/
2126
@Provides
2227
fun providePropertyListViewModelFlow(
2328
fragment: Fragment,
2429
factory: PropertyListFlowViewModelFactory
2530
) =
2631
ViewModelProvider(fragment, factory).get(PropertyListViewModelFlow::class.java)
2732

33+
/**
34+
* Property ViewModel that uses Flow for data operation with Pagaination
35+
*/
36+
@Provides
37+
fun providePagedPropertyListViewModel(
38+
fragment: Fragment,
39+
factory: PagedPropertyListViewModelFactory
40+
) =
41+
ViewModelProvider(fragment, factory).get(PagedPropertyListViewModel::class.java)
42+
43+
/**
44+
* Property ViewModel that uses Rxjava for data operations
45+
*/
2846
@Provides
2947
fun providePropertyListViewModelRxJava3(
3048
fragment: Fragment,

features/home/src/main/java/com/smarttoolfactory/home/viewmodel/AbstractPropertyListVM.kt renamed to features/home/src/main/java/com/smarttoolfactory/home/propertylist/AbstractPropertyListVM.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.smarttoolfactory.home.viewmodel
1+
package com.smarttoolfactory.home.propertylist
22

33
import androidx.lifecycle.LiveData
44
import androidx.lifecycle.ViewModel

0 commit comments

Comments
 (0)