Mehedi Hassan Piash [Sr. Software Engineer]

September 22, 2021

Bottom navigation in android[Koltin]

September 22, 2021 Posted by Piash , No comments

Bottom navigation with the android navigation component is a little bit tricky. I found most of the resources are activity-based. I wanna implement it inside fragment with navigation component. So let’s start step by step…

Bottom navigation screenshoot
  1. res/menu/bottom_navigation_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/detailFragmentBottom"
android:enabled="true"
android:icon="@drawable/ic_home_24"
android:title="@string/home"/>
<item
android:id="@+id/imagePickerFragmentBottom"
android:enabled="true"
android:icon="@drawable/ic_music_note_24"
android:title="@string/music"/>
<item
android:id="@+id/expandableRecyclerViewFragmentBottom"
android:enabled="true"
android:icon="@drawable/ic_article_24"
android:title="@string/article"/>
</menu>

2. res/navigation/bottom_navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
android:id="@+id/bottom_navigation"
app:startDestination="@id/detailFragmentBottom">

<fragment
android:id="@+id/detailFragmentBottom"
android:name="com.piashcse.experiment.mvvm_hilt.ui.fragment.DetailFragment"
android:label="DetailFragment" />
<fragment
android:id="@+id/expandableRecyclerViewFragmentBottom"
android:name="com.piashcse.experiment.mvvm_hilt.ui.fragment.ExpandableRecyclerViewFragment"
android:label="fragment_expandable_recycler_view"
tools:layout="@layout/fragment_expandable_recycler_view" />
<fragment
android:id="@+id/imagePickerFragmentBottom"
android:name="com.piashcse.experiment.mvvm_hilt.ui.fragment.ImagePickerFragment"
android:label="fragment_image_picker"
tools:layout="@layout/fragment_image_picker" />

</navigation>

3. res/layout/fragment_bottom_navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.fragment.BottomNavigationFragment">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_bottom_navigation"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/bottom_navigation" />

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_navigation_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

4. BottomNavigationFragment.kt

package com.piashcse.experiment.mvvm_hilt.ui.fragment

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.piashcse.experiment.mvvm_hilt.R
import com.piashcse.experiment.mvvm_hilt.databinding.FragmentBottomNavigationBinding
import com.piashcse.experiment.mvvm_hilt.utils.hide
import com.piashcse.experiment.mvvm_hilt.utils.show
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class BottomNavigationFragment : Fragment() {
private var _binding: FragmentBottomNavigationBinding? = null
private val binding get() = requireNotNull(_binding) // or !!_binding
private lateinit var navController: NavController
private lateinit var navHostFragment: NavHostFragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentBottomNavigationBinding.inflate(layoutInflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}

fun initView() {
navHostFragment =
childFragmentManager.findFragmentById(R.id.nav_host_fragment_bottom_navigation) as NavHostFragment
navController = navHostFragment.navController
binding.bottomNavigation.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.detailFragmentBottom, R.id.expandableRecyclerViewFragmentBottom, R.id.imagePickerFragmentBottom -> {
binding.bottomNavigation.show()
}
else ->
binding.bottomNavigation.hide()
}
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

}

Github: https://github.com/piashcse/blog_piashcse_code/tree/master/MVVM_Hilt

Ref: https://piashcse.medium.com/bottom-navigation-in-android-koltin-42c746b3ddf4

September 17, 2021

Expandable recyclerView in kotlin

September 17, 2021 Posted by Piash , No comments

 There are so many use cases for expandable recyclerView for example where there is a representation of category with subcategories or parent with a group of Childs. Let’s start coding step by step….

Image: Screen shot of expandable revcyclerView
  1. item_child.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="32dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:textSize="12sp"
android:textColor="@color/parent_divider_color"
android:letterSpacing="0.2"
tools:text="@string/title" />
</LinearLayout>

2. item_parent

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">


<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="15sp"
android:gravity="center"
android:letterSpacing="0.2"
tools:text="@string/title" />
<View
android:layout_width="150dp"
android:layout_height="2dp"
android:layout_gravity="center"
android:background="@color/parent_divider_color"/>

<ImageView
android:id="@+id/down_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
app:srcCompat="@drawable/ic_baseline_arrow_down_24"/>

</LinearLayout>

3. fragment_expandable_recycler_view.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.fragment.ExpandableRecyclerViewFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/expandable_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</androidx.appcompat.widget.LinearLayoutCompat>

4. Items.kt

package com.piashcse.experiment.mvvm_hilt.model.expandable

interface Item {
fun getItemType(): Int
}

const val PARENT = 0
const val CHILD = 1

data class Parent(val parentItem: String) : Item {
var parent = parentItem
val childItems = ArrayList<Child>()
var isExpanded = false
var selectedChild: Child? = null

override fun getItemType() = PARENT
}

data class Child(
val parent: Parent,
val id: Int,
val title: String,
var price: Int = 0) : Item {
override fun getItemType() = CHILD
var isSelected: Boolean = false
}

data class Node(val id: Long, val parent: Node?) {

var isExpanded = false
val childList = ArrayList<Node>()
var nestingLevel = 0

init {
calculateNestingLevel()
}

private fun calculateNestingLevel() {
var current = parent
while (current != null) {
nestingLevel++
current = current.parent
}
}
}

5. ExpandableCategoryAdapter.kt

package com.piashcse.experiment.mvvm_hilt.ui.adapter

import com.piashcse.experiment.mvvm_hilt.model.expandable.Item
import com.piashcse.experiment.mvvm_hilt.model.expandable.Parent

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.piashcse.experiment.mvvm_hilt.R
import com.piashcse.experiment.mvvm_hilt.databinding.ItemChildBinding
import com.piashcse.experiment.mvvm_hilt.databinding.ItemParentBinding
import com.piashcse.experiment.mvvm_hilt.model.expandable.CHILD
import com.piashcse.experiment.mvvm_hilt.model.expandable.Child
import timber.log.Timber

class ExpandableCategoryAdapter(
private val context: Context,
private val itemList: ArrayList<Item>
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var currentOpenedParent: Parent? = null
var onItemClick: ((Child) -> Unit)? = null


override fun getItemViewType(position: Int): Int {
return itemList[position].getItemType()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
CHILD -> {
val bind =
ItemChildBinding.inflate(LayoutInflater.from(parent.context), parent, false)
ChildViewHolder(
bind
)
}
else -> {
val bind =
ItemParentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
ParentViewHolder(bind)
}
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
CHILD -> {
val childViewHolder = (holder as ChildViewHolder)
val childItem = itemList[position] as Child
childViewHolder.bind(childItem)
}
else -> {
val parentViewHolder = holder as ParentViewHolder
val parentItem = itemList[position] as Parent
parentViewHolder.bind(parentItem)
}
}
}

override fun getItemCount() = itemList.size

inner class ParentViewHolder(val binding: ItemParentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(parentItem: Parent) {
updateViewState(parentItem)
itemView.setOnClickListener {
val startPosition = adapterPosition + 1
val count = parentItem.childItems.size

if (parentItem.isExpanded) {
itemList.removeAll(parentItem.childItems)
notifyItemRangeRemoved(startPosition, count)
parentItem.isExpanded = false
currentOpenedParent = null

} else {
itemList.addAll(startPosition, parentItem.childItems)
notifyItemRangeInserted(startPosition, count)
parentItem.isExpanded = true

currentOpenedParent?.let { openParent ->
itemList.removeAll(openParent.childItems)
notifyItemRangeRemoved(
itemList.indexOf(openParent) + 1,
currentOpenedParent!!.childItems.size
)
currentOpenedParent?.isExpanded = false
notifyItemChanged(itemList.indexOf(openParent))
}

currentOpenedParent = parentItem
}
updateViewState(parentItem)
}
}

private fun updateViewState(parentItem: Parent) {
binding.apply {
if (parentItem.selectedChild != null) {
title.text = parentItem.selectedChild?.title
title.setTextColor(
ContextCompat.getColor(
context,
R.color.parent_divider_color
)
)
return
}
if (parentItem.isExpanded) {
downArrow.rotation = 180f
} else {
downArrow.rotation = 0f
}
title.text = parentItem.parent
}
}
}

inner class ChildViewHolder(val binding: ItemChildBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItem: Child) {
binding.apply {
title.text = childItem.title
if (childItem.isSelected) {
title.setTextColor(
ContextCompat.getColor(
context,
R.color.sender_bubble_text_color
)
)
} else {
title.setTextColor(
ContextCompat.getColor(
context,
R.color.parent_divider_color
)
)
}
}
itemView.setOnClickListener {
Timber.d("child item: ${childItem.isSelected}")
binding.apply {
if (childItem.isSelected) {
title.setTextColor(
ContextCompat.getColor(
context,
R.color.parent_divider_color
)
)
childItem.isSelected = false
} else {
title.setTextColor(
ContextCompat.getColor(
context,
R.color.sender_bubble_text_color
)
)
childItem.isSelected = true
}
}

onItemClick?.invoke(childItem)
}
}
}
}

6. ExpandableRecyclerViewFragment.kt

package com.piashcse.experiment.mvvm_hilt.ui.fragment

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.piashcse.experiment.mvvm_hilt.databinding.FragmentExpandableRecyclerViewBinding
import com.piashcse.experiment.mvvm_hilt.model.expandable.Child
import com.piashcse.experiment.mvvm_hilt.model.expandable.Item
import com.piashcse.experiment.mvvm_hilt.model.expandable.Parent
import com.piashcse.experiment.mvvm_hilt.ui.adapter.ExpandableCategoryAdapter


class ExpandableRecyclerViewFragment : Fragment() {
private var _binding: FragmentExpandableRecyclerViewBinding? = null
private val binding get() = requireNotNull(_binding) // or _binding!!
private lateinit var expandableAdapter: ExpandableCategoryAdapter
private val expandableItems = ArrayList<Item>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentExpandableRecyclerViewBinding.inflate(layoutInflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}

private fun initView() {
for (i in 1..5) {
val parent = Parent("Parent sequence $i")
val childItems = arrayListOf<Child>()
for (j in 1..5) {
childItems.add(Child(parent, j, "Child sequence $j"))
}
parent.childItems.addAll(childItems)
expandableItems.add(parent)
}
expandableAdapter = ExpandableCategoryAdapter(requireContext(), expandableItems)
binding.expandableRecycler.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = expandableAdapter
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

}

Ref: https://piashcse.medium.com/expandable-recyclerview-in-kotlin-876d14ecedcd

github: https://github.com/piashcse/blog_piashcse_code

September 14, 2021

RecyclerView with Multiple viewTypes and layouts

September 14, 2021 Posted by Piash , No comments

There is a common feature of e-commerce app product page is multiple view of recyclerView with different layout. This feature is little bit tricky in terms of recyclerView. Today we will implement recyclerView with multiple viewTypes and layouts. Let’s start step by step…

  1. adapter_parent_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools">

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/child_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:itemCount="5"
tools:listitem="@layout/adapter_child_vertical_layout" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

2. ParentAdapter

package com.piashcse.experiment.mvvm_hilt.ui.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterParentLayoutBinding
import com.piashcse.experiment.mvvm_hilt.model.ChildData
import com.piashcse.experiment.mvvm_hilt.model.ParentData
import com.piashcse.experiment.mvvm_hilt.utils.dpToPx

class ParentAdapter(val context: Context) : RecyclerView.Adapter<ParentAdapter.ParentViewHolder>() {
private var numberOfTab: MutableList<ParentData> = arrayListOf()
private lateinit var childAdapter: ChildAdapter
private var customViewType: Int = AppConstants.ViewType.LIST_VIEW_TYPE
fun addItems(
newItems: MutableList<ParentData>?,
clearPreviousItem: Boolean = false
) {
newItems?.let {
if (clearPreviousItem) {
numberOfTab.clear()
numberOfTab.addAll(newItems)
} else {
numberOfTab.addAll(newItems)
}

notifyDataSetChanged()
}
}

fun changeViewType(viewType: Int) {
customViewType = viewType
notifyDataSetChanged()
}

fun getCustomViewType(): Int {
return childAdapter.getCustomViewType()
}

inner class ParentViewHolder(val binding: AdapterParentLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItems: ArrayList<ChildData>) {
childAdapter = ChildAdapter()
binding.childRecycler.apply {
if (customViewType == AppConstants.ViewType.LIST_VIEW_TYPE) {
layoutManager = GridLayoutManager(context, 1)
setPadding(10.dpToPx(), 0, 10.dpToPx(), 0)
} else {
layoutManager = GridLayoutManager(context, 2)
setPadding(5.dpToPx(), 0, 5.dpToPx(), 0)
}
childAdapter.changeViewType(customViewType)
adapter = childAdapter
}
childAdapter.addItems(childItems)
}
}


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {
val bind =
AdapterParentLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ParentViewHolder(bind)
}

override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
holder.bind(numberOfTab[position].childItems)
}

override fun getItemCount() = numberOfTab.size
}

3. adapter_child_vertical_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>

<variable
name="item"
type="com.piashcse.experiment.mvvm_hilt.model.ChildData" />
</data>

<androidx.appcompat.widget.LinearLayoutCompat 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:layout_marginBottom="10dp"
android:orientation="vertical">

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:orientation="vertical">

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.title}"
tools:text="@string/title" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Double.toString(item.price)}"
tools:text="@string/price" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/is_stock" />
</androidx.appcompat.widget.LinearLayoutCompat>

<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="150dp"
android:scaleType="centerCrop"
app:srcCompat="@drawable/image_nature" />

</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

4. adapter_child_horizontal_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>

<variable
name="item"
type="com.piashcse.experiment.mvvm_hilt.model.ChildData" />
</data>

<androidx.appcompat.widget.LinearLayoutCompat 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:orientation="horizontal"
android:weightSum="2">

<androidx.appcompat.widget.AppCompatImageView
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:scaleType="centerCrop"
app:srcCompat="@drawable/image_nature" />

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginStart="15dp"
android:layout_weight="1"
android:orientation="vertical">

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.title}"
tools:text="@string/title" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Double.toString(item.price)}"
tools:text="@string/price" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/is_stock" />
</androidx.appcompat.widget.LinearLayoutCompat>


</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

5. ChildAdapter

package com.piashcse.experiment.mvvm_hilt.ui.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterChildHorizontalLayoutBinding
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterChildVerticalLayoutBinding
import com.piashcse.experiment.mvvm_hilt.model.ChildData

class ChildAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items: MutableList<ChildData> = arrayListOf()
private var customVIewType: Int = AppConstants.ViewType.LIST_VIEW_TYPE

fun addItems(newItems: MutableList<ChildData>?, clearPreviousItem: Boolean = false) {
newItems?.let {
if (clearPreviousItem) {
items.clear()
items.addAll(newItems)
} else {
items.addAll(newItems)
}
notifyDataSetChanged()
}
}

fun changeViewType(viewType: Int) {
customVIewType = viewType
}

fun getCustomViewType(): Int {
return customVIewType
}

inner class VerticalViewHolder(val binding: AdapterChildVerticalLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItem: ChildData) {
binding.apply {
item = childItem
executePendingBindings()
}
}
}

inner class HorizontalViewHolder(val binding: AdapterChildHorizontalLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItem: ChildData) {
binding.apply {
item = childItem
executePendingBindings()
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (customVIewType == AppConstants.ViewType.LIST_VIEW_TYPE) {
val binding = AdapterChildHorizontalLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
HorizontalViewHolder(binding)
} else {
val binding = AdapterChildVerticalLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VerticalViewHolder(binding)
}
}


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is VerticalViewHolder) {
holder.bind(items[position])
} else if (holder is HorizontalViewHolder) {
holder.bind(items[position])
}
}

override fun getItemCount() = items.size

}

6. FragmentViewPagerWithNestedRecyclerView

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_default"
tools:context=".ui.fragment.ViewPagerWithNestedRecyclerViewFragment">

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/view_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/view_change"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.appcompat.widget.LinearLayoutCompat>

7. ViewPagerWithNestedRecyclerViewFragment

package com.piashcse.experiment.mvvm_hilt.ui.fragment

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.tabs.TabLayoutMediator
import com.piashcse.experiment.mvvm_hilt.databinding.FragmentViewPagerWithNestedRecyclerViewBinding
import com.piashcse.experiment.mvvm_hilt.ui.adapter.ParentAdapter
import dagger.hilt.android.AndroidEntryPoint
import android.widget.TextView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.model.ChildData
import com.piashcse.experiment.mvvm_hilt.model.ParentData


@AndroidEntryPoint
class ViewPagerWithNestedRecyclerViewFragment : Fragment() {
private var _binding: FragmentViewPagerWithNestedRecyclerViewBinding? = null
private val binding get() = requireNotNull(_binding) // or _binding!!
private val parentAdapter: ParentAdapter by lazy {
ParentAdapter(requireContext())
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentViewPagerWithNestedRecyclerViewBinding.inflate(
layoutInflater,
container,
false
)

return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}

private fun initView() {
binding.apply {

viewPager.apply {
adapter = parentAdapter
parentAdapter.addItems(generateData())
}
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = generateData()[position].title
}.attach()
setAllCaps(tabLayout, false)

viewChange.setOnClickListener {
if (parentAdapter.getCustomViewType() == AppConstants.ViewType.LIST_VIEW_TYPE) {
parentAdapter.changeViewType(AppConstants.ViewType.GRID_VIEW_TYPE)
} else {
parentAdapter.changeViewType(AppConstants.ViewType.LIST_VIEW_TYPE)
}
}
}
}

private fun setAllCaps(view: View?, caps: Boolean) {
if (view is ViewGroup) {
for (i in 0 until view.childCount) setAllCaps(view.getChildAt(i), caps)
} else if (view is TextView) view.isAllCaps = caps
}

private fun generateData(): MutableList<ParentData> {
return mutableListOf(
ParentData(
"Tab One", arrayListOf(
ChildData("Shirt-Red", 200.0, true, ""),
ChildData("Shirt-Blue", 300.0, true, "")
)
),
ParentData(
"Tab Tow", arrayListOf(
ChildData("Pant-Formal", 300.0, true, ""),
ChildData("Pant-Jeans", 400.0, true, "")
)
),
ParentData(
"Tab Two", arrayListOf(
ChildData("T-Shirt-Grey", 150.0, true, ""),
ChildData("T-Shirt-Black", 200.0, true, "")
)
),
ParentData(
"Tab Three", arrayListOf(
ChildData("T-Shirt-Grey", 150.0, true, ""),
ChildData("T-Shirt-Black", 200.0, true, "")
)
)
)
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Ref: https://github.com/piashcse/blog_piashcse_code