Skip to content

Commit cc4f90e

Browse files
Merge tag 'v1.0.0-alpah01' into develop
v1.0.0-alpha01
2 parents cb4152f + 311d866 commit cc4f90e

27 files changed

+795
-276
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
66

77

8-
Sample project that build with MVVM clean architure and various coolest tech stack including RxJava3 and Coroutines Flow
8+
Sample project that build with MVVM clean architure and various cool tech stack 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
99

10+
| Posts with Stautus | Flow | RxJava3 | Dashboard
11+
| ------------------|-------------| -----|--------------|
12+
| <img src="./screenshots/post_with_status.png"/> | <img src="./screenshots/post_flow.png"/> | <img src="./screenshots/post_rxjava3.png"/> |<img src="./screenshots/dashboard.png"/> |
13+
14+
15+
### Overview
1016
* Gradle Kotlin DSL is used for setting up gradle files with ```buildSrc``` folder and extensions.
1117
* KtLint, Detekt, and Git Hooks is used for checking, and formatting code and validating code before commits.
1218
* Dagger Hilt, Dynamic Feature Modules with Navigation Components, ViewModel, Retrofit, Room, RxJava, Coroutines libraries adn dependencies are set up.
1319
* ```features``` and ```libraries``` folders are used to include android libraries and dynamic feature modules
1420
* In core module dagger hilt dependencies and ```@EntryPoint``` is created
21+
* Data module uses Retrofit and Room to provide Local and Remote data sources
22+
* Repository provides offline and remote fetch function with mapping and local save, delete and fetch functions
1523

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ class HomeFragment : DynamicNavigationFragment<FragmentHomeBinding>() {
107107
private val tabConfigurationStrategy =
108108
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
109109
when (position) {
110-
0 -> tab.text = "RxJava3"
111-
1 -> tab.text = "Flow"
112-
else -> tab.text = "Posts with Status"
110+
0 -> tab.text = "Posts with Status"
111+
1 -> tab.text = "Posts With Flow"
112+
else -> tab.text = "Posts With RxJava3"
113113
}
114114
}
115115
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ class HomeViewPager2FragmentStateAdapter(fragmentManager: FragmentManager, lifec
2424

2525
return when (position) {
2626

27-
// Post List with Rxjava3
27+
// Post List with Status
2828
0 -> NavHostContainerFragment.createNavHostContainerFragment(
2929
R.layout.fragment_navhost_post_list,
3030
R.id.nested_nav_host_fragment_post_list
3131
)
3232

3333
// Post List with Flow
3434
1 -> NavHostContainerFragment.createNavHostContainerFragment(
35-
R.layout.fragment_navhost_post_list,
35+
R.layout.fragment_navhost_post_list_flow,
3636
R.id.nested_nav_host_fragment_post_list
3737
)
3838

39-
// Post List with Status
39+
// Post List with Rxjava3
4040
else -> NavHostContainerFragment.createNavHostContainerFragment(
41-
R.layout.fragment_navhost_post_list,
41+
R.layout.fragment_navhost_post_list_rxjava3,
4242
R.id.nested_nav_host_fragment_post_list
4343
)
4444
}

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

Lines changed: 28 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,42 @@
11
package com.smarttoolfactory.home.adapter
22

3-
import android.view.LayoutInflater
4-
import android.view.ViewGroup
53
import android.widget.ImageButton
64
import androidx.annotation.LayoutRes
7-
import androidx.databinding.DataBindingUtil
85
import androidx.databinding.ViewDataBinding
96
import androidx.recyclerview.widget.DiffUtil
10-
import androidx.recyclerview.widget.ListAdapter
117
import androidx.recyclerview.widget.RecyclerView
8+
import com.smarttoolfactory.core.ui.adapter.BaseListAdapter
129
import com.smarttoolfactory.domain.model.Post
1310
import com.smarttoolfactory.home.BR
1411
import com.smarttoolfactory.home.R
15-
import kotlinx.android.synthetic.main.row_post.view.*
12+
import com.smarttoolfactory.home.databinding.RowPostBinding
1613

1714
class PostListAdapter(
18-
1915
@LayoutRes private val layoutId: Int,
2016
private val onItemClicked: ((Post) -> Unit)? = null,
2117
private val onLikeButtonClicked: ((Post) -> Unit)? = null
2218
) :
23-
ListAdapter<Post, PostListAdapter.CustomViewHolder<Post>>(
19+
BaseListAdapter<Post>(
20+
layoutId,
2421
PostDiffCallback()
2522
) {
2623

27-
override fun onCreateViewHolder(
28-
parent: ViewGroup,
29-
viewType: Int
30-
): CustomViewHolder<Post> {
31-
32-
val binding =
33-
DataBindingUtil.inflate<ViewDataBinding>(
34-
LayoutInflater.from(parent.context),
35-
layoutId,
36-
parent,
37-
false
38-
)
39-
40-
return CustomViewHolder<Post>(binding)
41-
.apply {
42-
onViewHolderCreated(this, binding)
43-
}
24+
override fun onViewHolderBound(
25+
binding: ViewDataBinding,
26+
item: Post,
27+
position: Int,
28+
payloads: MutableList<Any>
29+
) {
30+
binding.setVariable(BR.item, item)
4431
}
4532

4633
/**
4734
* Add click listener here to prevent setting listener after a ViewHolder every time
4835
* ViewHolder is scrolled and onBindViewHolder is called
4936
*/
50-
private fun onViewHolderCreated(
37+
override fun onViewHolderCreated(
5138
viewHolder: RecyclerView.ViewHolder,
39+
viewType: Int,
5240
binding: ViewDataBinding
5341
) {
5442

@@ -58,44 +46,28 @@ class PostListAdapter(
5846
}
5947
}
6048

61-
binding.root.ivLike.setOnClickListener { likeButton ->
62-
onLikeButtonClicked?.let { onLikeButtonClick ->
49+
if (binding is RowPostBinding) {
50+
51+
binding.ivLike.setOnClickListener { likeButton ->
52+
onLikeButtonClicked?.let { onLikeButtonClick ->
6353

64-
getItem(viewHolder.bindingAdapterPosition).apply {
65-
// Change like status of Post
66-
isFavorite = !isFavorite
67-
onLikeButtonClick(this)
54+
getItem(viewHolder.bindingAdapterPosition).apply {
55+
// Change like status of Post
56+
isFavorite = !isFavorite
57+
onLikeButtonClick(this)
6858

69-
// Set image source of like button
70-
val imageResource = if (isFavorite) {
71-
R.drawable.ic_baseline_thumb_up_24
72-
} else {
73-
R.drawable.ic_outline_thumb_up_24
59+
// Set image source of like button
60+
val imageResource = if (isFavorite) {
61+
R.drawable.ic_baseline_thumb_up_24
62+
} else {
63+
R.drawable.ic_outline_thumb_up_24
64+
}
65+
(likeButton as? ImageButton)?.setImageResource(imageResource)
7466
}
75-
(likeButton as? ImageButton)?.setImageResource(imageResource)
7667
}
7768
}
7869
}
7970
}
80-
81-
override fun onBindViewHolder(holder: CustomViewHolder<Post>, position: Int) {
82-
val item = getItem(position)
83-
holder.bindTo(item)
84-
}
85-
86-
class CustomViewHolder<T> constructor(
87-
private val binding: ViewDataBinding
88-
) :
89-
RecyclerView.ViewHolder(binding.root) {
90-
91-
fun bindTo(
92-
item: T
93-
) {
94-
// Bind item to layout to dispatch data to layout
95-
binding.setVariable(BR.item, item)
96-
binding.executePendingBindings()
97-
}
98-
}
9971
}
10072

10173
/**

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

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

33
import androidx.fragment.app.Fragment
44
import com.smarttoolfactory.core.di.CoreModuleDependencies
5+
import com.smarttoolfactory.home.postlist.PostListFlowFragment
6+
import com.smarttoolfactory.home.postlist.PostListRxJava3Fragment
57
import com.smarttoolfactory.home.postlist.PostListWithStatusFragment
68
import dagger.BindsInstance
79
import dagger.Component
@@ -13,6 +15,8 @@ import dagger.Component
1315
interface PostListComponent {
1416

1517
fun inject(postListWithStatusFragment: PostListWithStatusFragment)
18+
fun inject(postListFlowFragment: PostListFlowFragment)
19+
fun inject(postListRxJava3Fragment: PostListRxJava3Fragment)
1620

1721
@Component.Factory
1822
interface Factory {

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

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

33
import androidx.fragment.app.Fragment
44
import androidx.lifecycle.ViewModelProvider
5+
import com.smarttoolfactory.home.viewmodel.PostListFlowViewModelFactory
6+
import com.smarttoolfactory.home.viewmodel.PostListRxJava3ViewModelFactory
7+
import com.smarttoolfactory.home.viewmodel.PostListViewModelFlow
8+
import com.smarttoolfactory.home.viewmodel.PostListViewModelRxJava3
59
import com.smarttoolfactory.home.viewmodel.PostStatusViewModel
610
import com.smarttoolfactory.home.viewmodel.PostStatusViewModelFactory
711
import dagger.Module
@@ -16,6 +20,17 @@ import kotlinx.coroutines.SupervisorJob
1620
@Module
1721
class PostListModule {
1822

23+
@Provides
24+
fun providePostListViewModelFlow(fragment: Fragment, factory: PostListFlowViewModelFactory) =
25+
ViewModelProvider(fragment, factory).get(PostListViewModelFlow::class.java)
26+
27+
@Provides
28+
fun providePostListViewModelRxJava3(
29+
fragment: Fragment,
30+
factory: PostListRxJava3ViewModelFactory
31+
) =
32+
ViewModelProvider(fragment, factory).get(PostListViewModelRxJava3::class.java)
33+
1934
@Provides
2035
fun providePostListViewModel(fragment: Fragment, factory: PostStatusViewModelFactory) =
2136
ViewModelProvider(fragment, factory).get(PostStatusViewModel::class.java)
Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,89 @@
1-
// package com.smarttoolfactory.home.postlist
2-
//
3-
// import android.os.Bundle
4-
// import android.view.View
5-
// import androidx.core.os.bundleOf
6-
// import androidx.fragment.app.viewModels
7-
// import androidx.navigation.fragment.findNavController
8-
// import androidx.recyclerview.widget.LinearLayoutManager
9-
// import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
10-
// import com.smarttoolfactory.home.R
11-
// import com.smarttoolfactory.home.databinding.FragmentPostListBinding
12-
// import dagger.hilt.android.AndroidEntryPoint
13-
//
14-
// class PostListFlowFragment : DynamicNavigationFragment<FragmentPostListBinding>() {
15-
//
16-
// override fun getLayoutRes(): Int = R.layout.fragment_post_list
17-
//
18-
// private val viewModel: PostListViewModel by viewModels()
19-
//
20-
// private lateinit var postListAdapter: PostListAdapter
21-
//
22-
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
23-
// super.onViewCreated(view, savedInstanceState)
24-
// viewModel.getPosts()
25-
// }
26-
//
27-
// override fun bindViews() {
28-
// dataBinding.viewModel = viewModel
29-
//
30-
// dataBinding.recyclerView.apply {
31-
//
32-
// // Set Layout manager
33-
// this.layoutManager =
34-
// LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
35-
//
36-
// postListAdapter = PostListAdapter(
37-
// R.layout.row_post,
38-
// viewModel::onClick,
39-
// viewModel::onLikeButtonClick
40-
// )
41-
//
42-
// // Set RecyclerViewAdapter
43-
// this.adapter = postListAdapter
44-
// }
45-
//
46-
// val swipeRefreshLayout = dataBinding.swipeRefreshLayout
47-
//
48-
// swipeRefreshLayout.setOnRefreshListener {
49-
// swipeRefreshLayout.isRefreshing = false
50-
// viewModel.refreshPosts()
51-
// }
52-
//
53-
// subscribeGoToDetailScreen()
54-
// }
55-
//
56-
// private fun subscribeGoToDetailScreen() {
57-
//
58-
// viewModel.goToDetailScreen.observe(
59-
// viewLifecycleOwner,
60-
// {
61-
//
62-
// it.getContentIfNotHandled()?.let { post ->
63-
// val bundle = bundleOf("post" to post)
64-
//
65-
// findNavController().navigate(
66-
// R.id.nav_graph_post_detail,
67-
// bundle
68-
// )
69-
// }
70-
// }
71-
// )
72-
// }
73-
// }
1+
package com.smarttoolfactory.home.postlist
2+
3+
import android.os.Bundle
4+
import androidx.core.os.bundleOf
5+
import androidx.navigation.fragment.findNavController
6+
import androidx.recyclerview.widget.GridLayoutManager
7+
import com.smarttoolfactory.core.di.CoreModuleDependencies
8+
import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment
9+
import com.smarttoolfactory.home.R
10+
import com.smarttoolfactory.home.adapter.PostListAdapter
11+
import com.smarttoolfactory.home.databinding.FragmentPostListBinding
12+
import com.smarttoolfactory.home.di.DaggerPostListComponent
13+
import com.smarttoolfactory.home.viewmodel.PostListViewModelFlow
14+
import dagger.hilt.android.EntryPointAccessors
15+
import javax.inject.Inject
16+
17+
class PostListFlowFragment : DynamicNavigationFragment<FragmentPostListBinding>() {
18+
19+
@Inject
20+
lateinit var viewModel: PostListViewModelFlow
21+
22+
override fun getLayoutRes(): Int = R.layout.fragment_post_list
23+
24+
override fun onCreate(savedInstanceState: Bundle?) {
25+
initCoreDependentInjection()
26+
super.onCreate(savedInstanceState)
27+
viewModel.getPosts()
28+
}
29+
30+
override fun bindViews() {
31+
dataBinding.viewModel = viewModel
32+
33+
dataBinding.recyclerView.apply {
34+
35+
// Set Layout manager
36+
this.layoutManager =
37+
GridLayoutManager(requireContext(), 3)
38+
39+
// Set RecyclerViewAdapter
40+
this.adapter =
41+
PostListAdapter(R.layout.row_post_grid, viewModel::onClick)
42+
}
43+
44+
val swipeRefreshLayout = dataBinding.swipeRefreshLayout
45+
46+
swipeRefreshLayout.setOnRefreshListener {
47+
swipeRefreshLayout.isRefreshing = false
48+
viewModel.refreshPosts()
49+
}
50+
51+
subscribeGoToDetailScreen()
52+
}
53+
54+
private fun subscribeGoToDetailScreen() {
55+
56+
viewModel.goToDetailScreen.observe(
57+
viewLifecycleOwner,
58+
{
59+
60+
it.getContentIfNotHandled()?.let { post ->
61+
val bundle = bundleOf("post" to post)
62+
63+
/*
64+
Directly calling R.id.nav_graph_post_detail causes compiler to fail
65+
with Unresolved reference: nav_graph_post_detail
66+
*/
67+
findNavController().navigate(
68+
R.id.action_postListFragment_to_nav_graph_post_detail,
69+
bundle
70+
)
71+
}
72+
}
73+
)
74+
}
75+
76+
private fun initCoreDependentInjection() {
77+
78+
val coreModuleDependencies = EntryPointAccessors.fromApplication(
79+
requireActivity().applicationContext,
80+
CoreModuleDependencies::class.java
81+
)
82+
83+
DaggerPostListComponent.factory().create(
84+
dependentModule = coreModuleDependencies,
85+
fragment = this
86+
)
87+
.inject(this)
88+
}
89+
}

0 commit comments

Comments
 (0)