Skip to content

Commit

Permalink
Merge branch 'release/v1.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
SmartToolFactory committed Sep 9, 2020
2 parents 819231e + 6773587 commit 6f3850e
Show file tree
Hide file tree
Showing 48 changed files with 1,069 additions and 96 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ Sample project that build with MVVM clean architure and various cool techs inclu

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

| Flow | RxJava3 | Pagination | Favorites
| ------------------|-------------| -----|--------------|
| <img src="./screenshots/property_flow.png"/> | <img src="./screenshots/property_rxjava3.png"/> | <img src="./screenshots/property_pagination.png"/> |<img src="./screenshots/favorites.png"/> |

| Flow | RxJava3 | Pagination |
| ------------------|-------------| -----|
| <img src="./screenshots/property_flow.png"/> | <img src="./screenshots/property_rxjava3.png"/> | <img src="./screenshots/property_pagination.png"/> |

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

<img src="/./screenshots/property.gif" align="right" width="32%"/>

## Built With 🛠

Some of the popular libraries and MVVM clean architecture used with offline-first and offline-last with Room database and Retrofit as data source
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ android {

dynamicFeatures = mutableSetOf(
Modules.DynamicFeature.HOME,
Modules.DynamicFeature.PROPERTY_DETAIL,
Modules.DynamicFeature.FAVORITES,
Modules.DynamicFeature.NOTIFICATION,
Modules.DynamicFeature.ACCOUNT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ package com.smarttoolfactory.propertyfindar

import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
import com.smarttoolfactory.core.util.observe
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
import com.smarttoolfactory.propertyfindar.databinding.FragmentMainBinding
import com.smarttoolfactory.propertyfindar.ui.BottomNavigationFragmentStateAdapter

class MainFragment : DynamicNavigationFragment<FragmentMainBinding>() {

/**
* ViewModel for navigating to property detail screen from Main Fragment
*/
private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()

override fun getLayoutRes(): Int = R.layout.fragment_main

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -51,7 +62,26 @@ class MainFragment : DynamicNavigationFragment<FragmentMainBinding>() {
}
}
}
false
subscribePropertyDetailNavigation()
}

/**
* Navigates to Property Detail fragment from this fragment that replacing main fragment
* that contains [BottomNavigationView]
*/
private fun subscribePropertyDetailNavigation() {
viewLifecycleOwner.observe(propertyDetailNavigationVM.goToPropertyDetailFromMain) {

it.getContentIfNotHandled()?.let { propertyItem ->
val bundle = bundleOf("property" to propertyItem)

findNavController()
.navigate(
R.id.action_mainFragment_to_propertyDetailFragment,
bundle
)
}
}
}

override fun onDestroyView() {
Expand Down
23 changes: 23 additions & 0 deletions app/src/main/res/anim/fade_in.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>
23 changes: 23 additions & 0 deletions app/src/main/res/anim/fade_out.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2018 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
7 changes: 7 additions & 0 deletions app/src/main/res/anim/slide_in_left.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate android:fromXDelta="-100%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
7 changes: 7 additions & 0 deletions app/src/main/res/anim/slide_in_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate android:fromXDelta="100%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
7 changes: 7 additions & 0 deletions app/src/main/res/anim/slide_out_left.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate android:fromXDelta="0%" android:toXDelta="-100%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
7 changes: 7 additions & 0 deletions app/src/main/res/anim/slide_out_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate android:fromXDelta="0%" android:toXDelta="100%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="700"/>
</set>
25 changes: 24 additions & 1 deletion app/src/main/res/navigation/nav_graph_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@
android:id="@+id/mainFragment"
android:name="com.smarttoolfactory.propertyfindar.MainFragment"
android:label="MainFragment"
tools:layout="@layout/fragment_main" />
tools:layout="@layout/fragment_main">

<action
android:id="@+id/action_mainFragment_to_propertyDetailFragment"
app:destination="@id/nav_graph_property_detail"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />

</fragment>


<!-- Property Detail dynamic feature module -->
<include-dynamic
android:id="@+id/nav_graph_property_detail"
android:name="com.smarttoolfactory.property_detail"
app:graphResName="nav_graph_property_detail"
app:moduleName="property_detail">

<argument
android:name="property"
app:argType="com.smarttoolfactory.domain.model.PropertyItem" />

</include-dynamic>
</navigation>
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fun DependencyHandler.addAppModuleDependencies() {
implementation(Deps.COROUTINES_ANDROID)

// Leak Canary
debugImplementation(Deps.LEAK_CANARY)
// debugImplementation(Deps.LEAK_CANARY)

// Room
implementation(Deps.ROOM_RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
import com.smarttoolfactory.core.util.Event
import com.smarttoolfactory.core.util.observe
import com.smarttoolfactory.core.viewmodel.NavControllerViewModel
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
import com.smarttoolfactory.home.adapter.HomeViewPager2FragmentStateAdapter
import com.smarttoolfactory.home.databinding.FragmentHomeBinding
import com.smarttoolfactory.home.viewmodel.HomeToolbarVM
Expand Down Expand Up @@ -53,6 +57,11 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
*/
private val toolbarVM by activityViewModels<HomeToolbarVM>()

/**
* ViewModel for navigating to property detail screen from Home Fragment
*/
private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()

override fun bindViews() {

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

subscribeAppbarNavigation()

subscribePropertyDetailNavigation()
}

/**
* Navigates to Property Detail fragment from this fragment that replacing main fragment
* that contains [BottomNavigationView]
*/
private fun subscribePropertyDetailNavigation() {
viewLifecycleOwner.observe(propertyDetailNavigationVM.goToPropertyDetailFromHome) {

it.getContentIfNotHandled()?.let { propertyItem ->
val bundle = bundleOf("property" to propertyItem)

findNavController()
.navigate(
R.id.action_home_dest_to_propertyDetailFragment,
bundle
)
}
}
}

private fun setToolbarMenuItemListener() {
Expand All @@ -98,7 +128,7 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
private fun subscribeAppbarNavigation() {
navControllerViewModel.currentNavController.observe(
viewLifecycleOwner,
Observer { it ->
{ it ->

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

private var currentItem = 0
private var checkedItem = currentItem
private var canceled = false

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

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

val displayNames = viewModel.sortPropertyList.toTypedArray()
currentItem = viewModel.sortPropertyList.indexOf(viewModel.currentSortFilter)
checkedItem = currentItem

displayNames[0] = "Featured"
canceled = false

val builder = AlertDialog.Builder(requireActivity())
builder.setTitle("Sorting")
.setNegativeButton("CANCEL") { dialog, which ->
canceled = true
dismiss()
}
.setSingleChoiceItems(displayNames, currentItem) { dialog, which ->
.setSingleChoiceItems(
viewModel.sortFilterNames.toTypedArray(),
currentItem
) { dialog, which ->
checkedItem = which
}.setOnDismissListener {

// Alternative 1 works as soon as user changes the option
// if (currentItem != checkedItem) {
// if (currentItem != checkedItem) {
// viewModel.queryBySort.value = Event(viewModel.sortPropertyList[checkedItem])
// }
// dismiss()
}
return builder.create()
}

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
// Alternative works after dialog is dismissed
if (currentItem != checkedItem) {
if (currentItem != checkedItem && !canceled) {
viewModel.queryBySort.value = Event(viewModel.sortPropertyList[checkedItem])
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.smarttoolfactory.core.di.CoreModuleDependencies
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
import com.smarttoolfactory.core.util.Event
import com.smarttoolfactory.core.util.observe
import com.smarttoolfactory.core.viewmodel.PropertyDetailNavigationVM
import com.smarttoolfactory.home.R
import com.smarttoolfactory.home.adapter.PropertyItemListAdapter
import com.smarttoolfactory.home.databinding.FragmentPropertyListBinding
Expand All @@ -20,6 +22,8 @@ class PropertyListFlowFragment : DynamicNavigationFragment<FragmentPropertyListB
@Inject
lateinit var viewModel: PropertyListViewModelFlow

private val propertyDetailNavigationVM by activityViewModels<PropertyDetailNavigationVM>()

lateinit var itemListAdapter: PropertyItemListAdapter

/**
Expand Down Expand Up @@ -92,7 +96,32 @@ class PropertyListFlowFragment : DynamicNavigationFragment<FragmentPropertyListB
{

it.getContentIfNotHandled()?.let { propertyItem ->

val bundle = bundleOf("property" to propertyItem)
/*
* This is the navController belong to Home
*/

// Alternative 1 getting grand grand parent fragment of this fragment
// try {
// val homeFragment = parentFragment?.parentFragment?.parentFragment
//
// (homeFragment as? HomeFragment)?.findNavController()?.navigate(
// R.id.action_home_dest_to_propertyDetailFragment,
// bundle
// )
//
// } catch (e: Exception) {
// findNavController()
// .navigate(
// R.id.action_propertyListFragment_to_nav_graph_property_detail,
// bundle
// )
// }

// Alternative 2 use ViewModel
propertyDetailNavigationVM.goToPropertyDetailFromMain.value =
(Event(propertyItem))
}
}
)
Expand Down
Loading

0 comments on commit 6f3850e

Please sign in to comment.