Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

September 14, 2021

RecyclerView with Multiple viewTypes and layouts

September 14, 2021 Posted by Mehedi Hassan 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

August 17, 2021

Check all properties of null of an object using reflection [kotlin]

August 17, 2021 Posted by Mehedi Hassan Piash No comments
 inline fun <reified T: Any>notNullProperties(data: T, callBack :(list: List<String>)->Unit){  
   val allNullData = mutableListOf<String>()  
   for (prop in T::class.memberProperties) {  
     if ( prop.get(data) == null){  
       allNullData.add(prop.name)  
     }  
   }  
   callBack.invoke(allNullData)  
 }  

  • Use case      

  val changePassword = ChangePassword()  
         notNullProperties(changePassword){  
           if (it.isEmpty()){  
             println("not null")  
           }else{  
             println("Null : $it")  
           }  
         }  

August 15, 2021

Kotlin : How to use if-else like condition with let block for 'null' checks

August 15, 2021 Posted by Mehedi Hassan Piash No comments

Source - kotlinlang.org

  • ?. performs a safe call (calls a method or accesses a property if the receiver is non-null)
  • ?: takes the right-hand value if the left-hand value is null (the elvis operator)

Change ?. with ?: will solve this issue,

Code base as following, will run either let or run block based upon the null check.

 var someValue : String? = null  
 someValue = "SOF"  
 someValue?.let {safeSomeValue->  
 //This code block will run only if someValue is not null  
 }?:run {  
 //This code block will run only when if someValue is null, like else condition  
 }  

1. You can create an extension function, like this

 fun <T> T?.whenNull(block: () -> Unit) = this ?: block()  

2. then you call it like this

 somevalue?.let { safeSomeValue ->  
   // TODO  
 }.whenNull {  
   // execute when someValue is null  
 }  

August 07, 2021

Search filter in recyclerView android

August 07, 2021 Posted by Mehedi Hassan Piash , No comments

 1. Adapter for filter search data 

 class CountryAdapter : RecyclerView.Adapter<CountryAdapter.CountryViewHolder>() {  
   private val items: ArrayList<CountryNameItem> = arrayListOf()  
   var onItemClick: ((CountryNameItem) -> Unit)? = null  
   private val itemsCopies: ArrayList<CountryNameItem> = arrayListOf()  
   fun addItems(newItems: List<CountryNameItem>) {  
     items.addAll(newItems)  
     itemsCopies.addAll(newItems)  
     notifyDataSetChanged()  
   }  
   override fun onCreateViewHolder(  
     parent: ViewGroup,  
     viewType: Int  
   ): CountryAdapter.CountryViewHolder {  
     val binding =  
       AdapterCountryItemLayoutBinding.inflate(  
         LayoutInflater.from(parent.context),  
         parent,  
         false  
       )  
     return CountryViewHolder(binding)  
   }  
   inner class CountryViewHolder(val binding: AdapterCountryItemLayoutBinding) :  
     RecyclerView.ViewHolder(binding.root) {  
     fun bind(item: CountryNameItem) {  
       binding.country = item  
       itemView.setOnClickListener {  
         onItemClick?.invoke(item)  
       }  
     }  
   }  
   override fun onBindViewHolder(holder: CountryAdapter.CountryViewHolder, position: Int) {  
     val item = items[position]  
     return holder.bind(item)  
   }  
   override fun getItemCount() = items.size  
   fun filter(sequence: CharSequence?) {  
     val temp: ArrayList<CountryNameItem> = arrayListOf()  
     if (sequence.isNullOrEmpty().not()) {  
       for (s in itemsCopies) {  
         if (s.name.toLowerCase().contains(sequence!!)) {  
           temp.add(s)  
         } else {  
           for (code in s.callingCodes) {  
             if (code.contains(sequence!!)) {  
               temp.add(s)  
             }  
           }  
         }  
       }  
     } else {  
       temp.addAll(itemsCopies)  
     }  
     items.clear()  
     items.addAll(temp)  
     notifyDataSetChanged()  
     temp.clear()  
   }  
 }  

 1. Use case of adapter in the search field 

 binding.countryFilter.doOnTextChanged { text, _, _, _ ->  
         countryAdapter.filter(text.toString())  
         text?.let {  
           if (it.isNotEmpty()) cancel.show() else cancel.hide()  
         }  
       }  
 binding.cancel.setOnClickListener {  
       binding.countryFilter.text?.clear()  
     }  

Read local json file from assets in android

August 07, 2021 Posted by Mehedi Hassan Piash , No comments

 1. Extension to read JSON from assets 

 // Read json file from assets  
 fun AssetManager.readAssetsFile(fileName: String): String =  
   open(fileName).bufferedReader().use { it.readText() }  

 2. Use case of reading JSON 

  requireActivity().assets.readAssetsFile("country.json").fromPrettyJson<CountryName>()  

 3.  JSON to object and object to JSON extension 

 // Transform simple object to String with Gson  
 inline fun <reified T : Any> T.toPrettyJson() : String = Gson().toJson(this, T::class.java)  
 // Transform String Json to Object  
 inline fun <reified T : Any> String.fromPrettyJson() : T = Gson().fromJson(this , T::class.java)  
 // Transform String List Json to Object  
 inline fun <reified T : Any> String.fromPrettyJsonList() : MutableList <T> = when( this.isNotEmpty()){  
   true -> Gson().fromJson(this, object : TypeToken<MutableList<T>>() {}.type)  
   false -> mutableListOf()  
 }