Skip to content

Commit 6f3850e

Browse files
Merge branch 'release/v1.0.0'
2 parents 819231e + 6773587 commit 6f3850e

File tree

48 files changed

+1069
-96
lines changed

Some content is hidden

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

48 files changed

+1069
-96
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ Sample project that build with MVVM clean architure and various cool techs inclu
1111

1212
Unit tests are written with JUnit4, JUnit5, MockK, Truth, MockWebServer.
1313

14-
| Flow | RxJava3 | Pagination | Favorites
15-
| ------------------|-------------| -----|--------------|
16-
| <img src="./screenshots/property_flow.png"/> | <img src="./screenshots/property_rxjava3.png"/> | <img src="./screenshots/property_pagination.png"/> |<img src="./screenshots/favorites.png"/> |
17-
14+
| Flow | RxJava3 | Pagination |
15+
| ------------------|-------------| -----|
16+
| <img src="./screenshots/property_flow.png"/> | <img src="./screenshots/property_rxjava3.png"/> | <img src="./screenshots/property_pagination.png"/> |
1817

1918
## Overview
2019
* Gradle Kotlin DSL is used for setting up gradle files with ```buildSrc``` folder and extensions.
@@ -27,6 +26,8 @@ Unit tests are written with JUnit4, JUnit5, MockK, Truth, MockWebServer.
2726
* Domain module uses useCase classes to implment business logic to fetch and forward data
2827
* ViewModel uses LiveData with data-binding to display LOADING, and ERROR or SUCCESS states.
2928

29+
<img src="/./screenshots/property.gif" align="right" width="32%"/>
30+
3031
## Built With 🛠
3132

3233
Some of the popular libraries and MVVM clean architecture used with offline-first and offline-last with Room database and Retrofit as data source

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ android {
9797

9898
dynamicFeatures = mutableSetOf(
9999
Modules.DynamicFeature.HOME,
100+
Modules.DynamicFeature.PROPERTY_DETAIL,
100101
Modules.DynamicFeature.FAVORITES,
101102
Modules.DynamicFeature.NOTIFICATION,
102103
Modules.DynamicFeature.ACCOUNT

app/src/main/java/com/smarttoolfactory/propertyfindar/MainFragment.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,23 @@ package com.smarttoolfactory.propertyfindar
22

33
import android.os.Bundle
44
import android.view.View
5+
import androidx.core.os.bundleOf
6+
import androidx.fragment.app.activityViewModels
7+
import androidx.navigation.fragment.findNavController
8+
import com.google.android.material.bottomnavigation.BottomNavigationView
59
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
10+
import com.smarttoolfactory.core.util.observe
11+
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
612
import com.smarttoolfactory.propertyfindar.databinding.FragmentMainBinding
713
import com.smarttoolfactory.propertyfindar.ui.BottomNavigationFragmentStateAdapter
814

915
class MainFragment : DynamicNavigationFragment<FragmentMainBinding>() {
1016

17+
/**
18+
* ViewModel for navigating to property detail screen from Main Fragment
19+
*/
20+
private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()
21+
1122
override fun getLayoutRes(): Int = R.layout.fragment_main
1223

1324
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -51,7 +62,26 @@ class MainFragment : DynamicNavigationFragment<FragmentMainBinding>() {
5162
}
5263
}
5364
}
54-
false
65+
subscribePropertyDetailNavigation()
66+
}
67+
68+
/**
69+
* Navigates to Property Detail fragment from this fragment that replacing main fragment
70+
* that contains [BottomNavigationView]
71+
*/
72+
private fun subscribePropertyDetailNavigation() {
73+
viewLifecycleOwner.observe(propertyDetailNavigationVM.goToPropertyDetailFromMain) {
74+
75+
it.getContentIfNotHandled()?.let { propertyItem ->
76+
val bundle = bundleOf("property" to propertyItem)
77+
78+
findNavController()
79+
.navigate(
80+
R.id.action_mainFragment_to_propertyDetailFragment,
81+
bundle
82+
)
83+
}
84+
}
5585
}
5686

5787
override fun onDestroyView() {

app/src/main/res/anim/fade_in.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (C) 2018 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<set xmlns:android="http://schemas.android.com/apk/res/android">
19+
<alpha
20+
android:duration="@android:integer/config_mediumAnimTime"
21+
android:fromAlpha="0.0"
22+
android:toAlpha="1.0"/>
23+
</set>

app/src/main/res/anim/fade_out.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (C) 2018 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<set xmlns:android="http://schemas.android.com/apk/res/android">
19+
<alpha
20+
android:duration="@android:integer/config_mediumAnimTime"
21+
android:fromAlpha="1.0"
22+
android:toAlpha="0.0"/>
23+
</set>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<translate android:fromXDelta="-100%" android:toXDelta="0%"
5+
android:fromYDelta="0%" android:toYDelta="0%"
6+
android:duration="700"/>
7+
</set>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<translate android:fromXDelta="100%" android:toXDelta="0%"
5+
android:fromYDelta="0%" android:toYDelta="0%"
6+
android:duration="700"/>
7+
</set>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<translate android:fromXDelta="0%" android:toXDelta="-100%"
5+
android:fromYDelta="0%" android:toYDelta="0%"
6+
android:duration="700"/>
7+
</set>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<set xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<translate android:fromXDelta="0%" android:toXDelta="100%"
5+
android:fromYDelta="0%" android:toYDelta="0%"
6+
android:duration="700"/>
7+
</set>

app/src/main/res/navigation/nav_graph_main.xml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@
99
android:id="@+id/mainFragment"
1010
android:name="com.smarttoolfactory.propertyfindar.MainFragment"
1111
android:label="MainFragment"
12-
tools:layout="@layout/fragment_main" />
12+
tools:layout="@layout/fragment_main">
1313

14+
<action
15+
android:id="@+id/action_mainFragment_to_propertyDetailFragment"
16+
app:destination="@id/nav_graph_property_detail"
17+
app:enterAnim="@anim/slide_in_right"
18+
app:exitAnim="@anim/slide_out_left"
19+
app:popEnterAnim="@anim/slide_in_left"
20+
app:popExitAnim="@anim/slide_out_right" />
21+
22+
</fragment>
23+
24+
25+
<!-- Property Detail dynamic feature module -->
26+
<include-dynamic
27+
android:id="@+id/nav_graph_property_detail"
28+
android:name="com.smarttoolfactory.property_detail"
29+
app:graphResName="nav_graph_property_detail"
30+
app:moduleName="property_detail">
31+
32+
<argument
33+
android:name="property"
34+
app:argType="com.smarttoolfactory.domain.model.PropertyItem" />
35+
36+
</include-dynamic>
1437
</navigation>

buildSrc/src/main/java/extension/DependencyHandlerExtension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fun DependencyHandler.addAppModuleDependencies() {
5353
implementation(Deps.COROUTINES_ANDROID)
5454

5555
// Leak Canary
56-
debugImplementation(Deps.LEAK_CANARY)
56+
// debugImplementation(Deps.LEAK_CANARY)
5757

5858
// Room
5959
implementation(Deps.ROOM_RUNTIME)

features/home/src/main/java/com/smarttoolfactory/home/HomeFragment.kt

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,25 @@ import android.app.Dialog
44
import android.content.DialogInterface
55
import android.os.Bundle
66
import androidx.appcompat.app.AlertDialog
7+
import androidx.core.os.bundleOf
78
import androidx.fragment.app.DialogFragment
89
import androidx.fragment.app.Fragment
910
import androidx.fragment.app.activityViewModels
10-
import androidx.lifecycle.Observer
1111
import androidx.lifecycle.ViewModelProvider
1212
import androidx.navigation.NavController
13+
import androidx.navigation.fragment.findNavController
1314
import androidx.navigation.ui.AppBarConfiguration
1415
import androidx.navigation.ui.setupWithNavController
1516
import androidx.viewpager2.adapter.FragmentStateAdapter
1617
import androidx.viewpager2.widget.ViewPager2
18+
import com.google.android.material.bottomnavigation.BottomNavigationView
1719
import com.google.android.material.tabs.TabLayout
1820
import com.google.android.material.tabs.TabLayoutMediator
1921
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
2022
import com.smarttoolfactory.core.util.Event
23+
import com.smarttoolfactory.core.util.observe
2124
import com.smarttoolfactory.core.viewmodel.NavControllerViewModel
25+
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
2226
import com.smarttoolfactory.home.adapter.HomeViewPager2FragmentStateAdapter
2327
import com.smarttoolfactory.home.databinding.FragmentHomeBinding
2428
import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
@@ -53,6 +57,11 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
5357
*/
5458
private val toolbarVM by activityViewModels<HomeToolbarVM>()
5559

60+
/**
61+
* ViewModel for navigating to property detail screen from Home Fragment
62+
*/
63+
private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()
64+
5665
override fun bindViews() {
5766

5867
// ViewPager2
@@ -79,6 +88,27 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
7988
setToolbarMenuItemListener()
8089

8190
subscribeAppbarNavigation()
91+
92+
subscribePropertyDetailNavigation()
93+
}
94+
95+
/**
96+
* Navigates to Property Detail fragment from this fragment that replacing main fragment
97+
* that contains [BottomNavigationView]
98+
*/
99+
private fun subscribePropertyDetailNavigation() {
100+
viewLifecycleOwner.observe(propertyDetailNavigationVM.goToPropertyDetailFromHome) {
101+
102+
it.getContentIfNotHandled()?.let { propertyItem ->
103+
val bundle = bundleOf("property" to propertyItem)
104+
105+
findNavController()
106+
.navigate(
107+
R.id.action_home_dest_to_propertyDetailFragment,
108+
bundle
109+
)
110+
}
111+
}
82112
}
83113

84114
private fun setToolbarMenuItemListener() {
@@ -98,7 +128,7 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
98128
private fun subscribeAppbarNavigation() {
99129
navControllerViewModel.currentNavController.observe(
100130
viewLifecycleOwner,
101-
Observer { it ->
131+
{ it ->
102132

103133
it?.let { event: Event<NavController?> ->
104134
event.getContentIfNotHandled()?.let { navController ->
@@ -153,6 +183,7 @@ class SortDialogFragment : DialogFragment() {
153183

154184
private var currentItem = 0
155185
private var checkedItem = currentItem
186+
private var canceled = false
156187

157188
override fun onCreate(savedInstanceState: Bundle?) {
158189
super.onCreate(savedInstanceState)
@@ -161,34 +192,34 @@ class SortDialogFragment : DialogFragment() {
161192

162193
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
163194

164-
val displayNames = viewModel.sortPropertyList.toTypedArray()
165195
currentItem = viewModel.sortPropertyList.indexOf(viewModel.currentSortFilter)
166196
checkedItem = currentItem
167-
168-
displayNames[0] = "Featured"
197+
canceled = false
169198

170199
val builder = AlertDialog.Builder(requireActivity())
171200
builder.setTitle("Sorting")
172201
.setNegativeButton("CANCEL") { dialog, which ->
202+
canceled = true
173203
dismiss()
174204
}
175-
.setSingleChoiceItems(displayNames, currentItem) { dialog, which ->
205+
.setSingleChoiceItems(
206+
viewModel.sortFilterNames.toTypedArray(),
207+
currentItem
208+
) { dialog, which ->
176209
checkedItem = which
177-
}.setOnDismissListener {
178210

179211
// Alternative 1 works as soon as user changes the option
180-
// if (currentItem != checkedItem) {
212+
// if (currentItem != checkedItem) {
181213
// viewModel.queryBySort.value = Event(viewModel.sortPropertyList[checkedItem])
182214
// }
183-
// dismiss()
184215
}
185216
return builder.create()
186217
}
187218

188219
override fun onDismiss(dialog: DialogInterface) {
189220
super.onDismiss(dialog)
190221
// Alternative works after dialog is dismissed
191-
if (currentItem != checkedItem) {
222+
if (currentItem != checkedItem && !canceled) {
192223
viewModel.queryBySort.value = Event(viewModel.sortPropertyList[checkedItem])
193224
}
194225
}

features/home/src/main/java/com/smarttoolfactory/home/propertylist/flow/PropertyListFlowFragment.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import androidx.fragment.app.activityViewModels
66
import androidx.recyclerview.widget.LinearLayoutManager
77
import com.smarttoolfactory.core.di.CoreModuleDependencies
88
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
9+
import com.smarttoolfactory.core.util.Event
910
import com.smarttoolfactory.core.util.observe
11+
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
1012
import com.smarttoolfactory.home.R
1113
import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
1214
import com.smarttoolfactory.home.databinding.FragmentPropertyListBinding
@@ -20,6 +22,8 @@ class PropertyListFlowFragment : DynamicNavigationFragment<FragmentPropertyListB
2022
@Inject
2123
lateinit var viewModel: PropertyListViewModelFlow
2224

25+
private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()
26+
2327
lateinit var itemListAdapter: PropertyItemListAdapter
2428

2529
/**
@@ -92,7 +96,32 @@ class PropertyListFlowFragment : DynamicNavigationFragment<FragmentPropertyListB
9296
{
9397

9498
it.getContentIfNotHandled()?.let { propertyItem ->
99+
95100
val bundle = bundleOf("property" to propertyItem)
101+
/*
102+
* This is the navController belong to Home
103+
*/
104+
105+
// Alternative 1 getting grand grand parent fragment of this fragment
106+
// try {
107+
// val homeFragment = parentFragment?.parentFragment?.parentFragment
108+
//
109+
// (homeFragment as? HomeFragment)?.findNavController()?.navigate(
110+
// R.id.action_home_dest_to_propertyDetailFragment,
111+
// bundle
112+
// )
113+
//
114+
// } catch (e: Exception) {
115+
// findNavController()
116+
// .navigate(
117+
// R.id.action_propertyListFragment_to_nav_graph_property_detail,
118+
// bundle
119+
// )
120+
// }
121+
122+
// Alternative 2 use ViewModel
123+
propertyDetailNavigationVM.goToPropertyDetailFromMain.value =
124+
(Event(propertyItem))
96125
}
97126
}
98127
)

0 commit comments

Comments
 (0)