Mehedi Hassan Piash [Sr. Software Engineer]

December 21, 2021

Collapsible toolbar programatically in android

December 21, 2021 Posted by Piash , No comments

Sometimes may need the toolbar collapsible in the initial stage or in a certain condition. In that time we implement the functionality programmatically. Here is the following code or function we can call in a particular condition.

fun setCollapsibleToolbar() {
val params = appBar.layoutParams as CoordinatorLayout.LayoutParams
val behavior = params.behavior as AppBarLayout.Behavior?
if (behavior != null) {
val valueAnimator: ValueAnimator = ValueAnimator.ofInt()
valueAnimator.interpolator = DecelerateInterpolator()
valueAnimator.addUpdateListener { animation ->
behavior.topAndBottomOffset = (animation.animatedValue as Int)!!
appBar.requestLayout()
}
valueAnimator.setIntValues(0, -900)
valueAnimator.duration = 400
valueAnimator.start()
}
}

Ref: https://piashcse.medium.com/collapsible-toolbar-programatically-in-android-988a94e6795e

 

December 18, 2021

Get child data in parent table by backReferencedOn in kotlin Exposed Ktor part-3

December 18, 2021 Posted by Piash , No comments

UserId is a foreign key in UserHasType table . Now if we want to get UserHasTypeTable data as child data in UsersTable we need to point it as val userType by UserHasTypeEntity backReferencedOn UserHasTypeTable.user_id

object UsersTable : IdTable<String>("users") {
override val id: Column<EntityID<String>> = text("id").uniqueIndex().entityId()
val user_name = text("user_name")
val email = text("email")
val password = text("password")
val mobile_number = text("mobile_number").nullable()
val email_verified_at = text("email_verified_at").nullable() // so far unkmown
val remember_token = text("remember_token").nullable()
val verification_code = text("verification_code").nullable() // verification_code
val created_at = datetime("created_at").defaultExpression(CurrentDateTime()) // UTC time
val updated_at = datetime("updated_at").nullable()
val is_verified = text("is_verified").nullable() // email verified by validation code
override val primaryKey = PrimaryKey(id)
}

class UsersEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UsersEntity>(UsersTable)
var user_name by UsersTable.user_name
var email by UsersTable.email
var password by UsersTable.password
var mobile_number by UsersTable.mobile_number
var email_verified_at by UsersTable.email_verified_at
var remember_token by UsersTable.remember_token
var verification_code by UsersTable.verification_code
var created_at by UsersTable.created_at
var updated_at by UsersTable.updated_at
var is_verified by UsersTable.is_verified
val userType by UserHasTypeEntity backReferencedOn UserHasTypeTable.user_id
fun userResponse() = UsersResponse(
id.value,
user_name,
email,
mobile_number,
email_verified_at,
remember_token,
is_verified,
userType.userHasTypeResponse()
)
}

data class UsersResponse(
val id: String,
val userName: String,
val email: String,
val mobileNumber: String?,
val emailVerifiedAt: String?,
val rememberToken: String?,
val isVerified: String?,
var userType: UserHasType
)

UserHasTypeTable and UserHasTypeEntity

object UserHasTypeTable : IdTable<String>("user_has_type") {
override val id: Column<EntityID<String>> = text("id").uniqueIndex().entityId()
val user_id = reference("user_id", UsersTable.id)
val user_type_id = text("user_type_id")
val created_at = text("created_at")
val updated_at = text("updated_at")
override val primaryKey = PrimaryKey(id)
}

class UserHasTypeEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UserHasTypeEntity>(UserHasTypeTable)
var user_id by UserHasTypeTable.user_id
var user_type_id by UserHasTypeTable.user_type_id
var created_at by UserHasTypeTable.created_at
var updated_at by UserHasTypeTable.updated_at
//var users by UsersEntity referencedOn UserHasTypeTable.user_id
fun userHasTypeResponse() = UserHasType(id.toString(), user_type_id)
}

data class UserHasType(
val id: String, val user_type_id: String
)

User controller

class UserController {
fun login(loginBody: LoginBody) = transaction {
val query = UsersTable.leftJoin(UserHasTypeTable).select { UsersTable.email eq loginBody.email }
val result = UsersEntity.wrapRows(query).first()
if(loginBody.password == result.password)
return@transaction result.userResponse()
else
null
}
}

UserRoute

fun Route.userRoute(userController: UserController) {
post("login") {
val loginBody = call.receive<LoginBody>()

val db = userController.login(loginBody)
db.let {
call.respond(JsonResponse.success(loginResponse,HttpStatusCode.OK))
}
}
}

Ref:  https://piashcse.medium.com/get-child-data-in-parent-table-by-backreferencedon-in-kotlin-exposed-ktor-part-3-80bb14675871

October 23, 2021

Kotlin Exposed create an entity with reference for Ktor part-2

October 23, 2021 Posted by Piash No comments

 

PostgreSql UserTable

UserTable Entity for Ktor

import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.dao.EntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Column

object UsersTable : IdTable<String>("users") {
override val id: Column<EntityID<String>> = text("user_id").uniqueIndex().entityId()
val user_name = text("user_name")
val email = text("email")
val password = text("password")
val mobile_number = text("mobile_number").nullable()
val email_verified_at = text("email_verified_at").nullable() // so far unkmown
val remember_token = text("remember_token").nullable()
val verification_code = text("verification_code").nullable() // verification_code
val created_at = text("created_at").nullable()
val updated_at = text("updated_at").nullable()
val is_verified = text("is_verified").nullable() // email verified by validation code
override val primaryKey = PrimaryKey(id)
}

class UsersEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UsersEntity>(UsersTable)
var userId by UsersTable.id
var user_name by UsersTable.user_name
var email by UsersTable.email
var password by UsersTable.password
var mobile_number by UsersTable.mobile_number
var email_verified_at by UsersTable.email_verified_at
var remember_token by UsersTable.remember_token
var verification_code by UsersTable.verification_code
var created_at by UsersTable.created_at
var updated_at by UsersTable.updated_at
var is_verified by UsersTable.is_verified
var userType by UserTypeEntity via UserTypeTable
fun userResponse() = UsersResponse(userId.value, user_name,email, mobile_number, email_verified_at, remember_token, created_at, updated_at, is_verified)
}
data class UsersResponse( val userId :String,
val userName :String,
val email :String,
val mobileNumber:String?,
val emailVerifiedAt :String?,
val rememberToken:String?,
val createdAt:String?,
val updatedAt :String?,
val isVerified :String?)

UserHasTypeTable Entity

import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.dao.EntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.Column

object UserHasTypeTable : IdTable<String>("user_has_type") {
override val id: Column<EntityID<String>> = text("user_has_type_id").uniqueIndex().entityId()
val user_id = text("user_id").references(UsersTable.id)
val user_type_id = text("user_type_id")
val created_at = text("created_at")
val updated_at = text("updated_at")
override val primaryKey = PrimaryKey(id)
}

class UserHasTypeEntity(id: EntityID<String>) : Entity<String>(id) {
companion object : EntityClass<String, UserHasTypeEntity>(UserHasTypeTable)
var id_ by UserHasTypeTable.id
var user_id by UserHasTypeTable.user_id
var user_type_id by UserHasTypeTable.user_type_id
var created_at by UserHasTypeTable.created_at
var updated_at by UserHasTypeTable.updated_at
var users by UsersEntity referencedOn UserHasTypeTable.user_id
}

Ref: https://piashcse.medium.com/kotlin-exposed-create-entity-with-reference-for-ktor-part-2-1905836c400c

October 20, 2021

PostgreSQL database connection in Ktor part-1

October 20, 2021 Posted by Piash , No comments
PostgreSQL database connection in Ktor. 

 build.gradle dependency

// Exposed ORM library
implementation "org.jetbrains.exposed:exposed-core:0.35.1"
implementation "org.jetbrains.exposed:exposed-dao:0.35.1"
implementation "org.jetbrains.exposed:exposed-jdbc:0.35.1"
implementation "org.postgresql:postgresql:42.2.2"
implementation 'com.zaxxer:HikariCP:3.4.2'

resource/hikari.properties

dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
dataSource.user=postgres
dataSource.password=p123
dataSource.databaseName=db_ktor
dataSource.portNumber=5432
dataSource.serverName=localh

Database helper class

object DatabaseFactory {
fun init() {
// Database.connect(hikari())
initDB()
transaction {
//create(Fruits, UserTable, ProductTable)
}
}

private fun initDB() {
// database connection is handled from hikari properties
val config = HikariConfig("/hikari.properties")
val ds = HikariDataSource(config)
Database.connect(ds)
}

// database connection for h2 d
private fun hikari(): HikariDataSource {
val config = HikariConfig()
config.driverClassName = "org.h2.Driver"
config.jdbcUrl = "jdbc:h2:file:~/documents/db/h2db"
config.maximumPoolSize = 3
config.isAutoCommit = false
config.transactionIsolation = "TRANSACTION_REPEATABLE_READ"
config.validate()
return HikariDataSource(config)
}
}

Application class

fun main() {
//val environment = System.getenv("KTOR_ENVIRONMENT") ?: "development"
val configName = "application.conf"
val appEngineEnv = applicationEngineEnvironment {
config = HoconApplicationConfig(ConfigFactory.load(configName))
log = LoggerFactory.getLogger("ktor.application")
module {
DatabaseFactory.init()
}
connector {
host = config.property("ktor.deployment.host").getString()
port = config.property("ktor.deployment.port").getString().toInt()
}
}

embeddedServer(Netty, appEngineEnv).start(wait = true)
}

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