I am using recyclerview with multi view type and using nested recyclerviews:
But its scrolling very slow. I also handled the child recyclerviews scroll position in onViewRecycled function:
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
super.onViewRecycled(holder)
//save horizontal scroll state
val key = getSectionID(holder.layoutPosition)
if (holder is HomeSectionsViewHolder) {
scrollStates[key] =
holder
.itemView
.findViewById<RecyclerView>(R.id.itemsList)
.layoutManager?.onSaveInstanceState()
}
}
And in the onBindViewHolder
val state = scrollStates[key]
if (state != null) {
itemBinding.itemsList.layoutManager?.onRestoreInstanceState(state)
} else {
itemBinding.itemsList.layoutManager?.scrollToPosition(0)
}
I also tried:
binding.rvHome.recycledViewPool.setMaxRecycledViews(0, 0);
I am using ListAdapter with DiffUtils. I also tried to set adapter to child recyclerviews in the onCreateViewHolder and submit the list from onBindViewHolder and also tried setting adapter from onBindViewHolder. But in all cases main recycler view recycling the views and lagging. I dont want to disable the recycling of the recyclerview.
Can anyone suggest me what i am doing wrong here.
Code:
class MainAdapter(
private val context: Context,
private val currentLocale: String,
private val currentCountry: String,
private val homeRepository: HomeRepository
) :
ListAdapter<Section, RecyclerView.ViewHolder>(Companion) {
var selectedCondition: List<Int?>? = emptyList()
var selectedBrand: MutableList<Int?>? = arrayListOf()
private var carousalListBinded: Boolean = false
private var carousalList: List<CarouselItem>? = null
private var banners: List<Banner>? = null
private var carousel: ImageCarousel? = null
private var COVIDMsg: String? = null
private var COVIDMsgColor: String? = null
private var tvCovidMessage: TextView? = null
private var categoryAdapter = CategoryAdapter(context)
companion object : DiffUtil.ItemCallback<Section>() {
override fun areItemsTheSame(oldItem: Section, newItem: Section): Boolean =
oldItem == newItem
override fun areContentsTheSame(oldItem: Section, newItem: Section): Boolean =
oldItem == newItem
const val VIEW_TYPE_CATEGORY = 0
const val VIEW_TYPE_ITEM_SECTION = 1
const val VIEW_TYPE_BRAND = 2
const val VIEW_TYPE_IMAGE = 3
const val VIEW_TYPE_ADVANCE_IMAGE = 4
const val VIEW_TYPE_ADVANCE_CATEGORY = 5
const val VIEW_TYPE_BANNER = 6
const val VIEW_TYPE_HOME_BOTTOM_SECTION = 7
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) {
VIEW_TYPE_CATEGORY -> {
val categoryViewHolder = CategoryViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_categories_item,
parent,
false
)
)
categoryViewHolder.initRecView()
return categoryViewHolder
}
VIEW_TYPE_ITEM_SECTION -> {
val homeSectionsViewHolder = HomeSectionsViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_item_section,
parent,
false
)
)
homeSectionsViewHolder.initRecView()
return homeSectionsViewHolder
}
VIEW_TYPE_BRAND -> {
val brandsViewHolder = BrandsViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_brands_item,
parent,
false
)
)
brandsViewHolder.initRecView()
return brandsViewHolder
}
VIEW_TYPE_IMAGE -> {
val imagesViewHolder = ImagesViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_brands_item,
parent,
false
)
)
imagesViewHolder.initRecView()
return imagesViewHolder
}
VIEW_TYPE_ADVANCE_IMAGE -> {
val advanceImagesViewHolder = AdvanceImagesViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_brands_item,
parent,
false
)
)
advanceImagesViewHolder.initRecView()
return advanceImagesViewHolder
}
VIEW_TYPE_ADVANCE_CATEGORY -> {
val categorySectionViewHolder = CategorySectionViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_item_section,
parent,
false
)
)
categorySectionViewHolder.initRecView()
return categorySectionViewHolder
}
VIEW_TYPE_BANNER -> {
val binding: HomeBannerItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_banner_item,
parent,
false
)
binding.carousel.post {
val numberItemsInColumn = 1
val collectionViewWidth = getWidth(context)
val widthPerCell = collectionViewWidth / numberItemsInColumn
val widthPerItem = widthPerCell
val cellHeight = getCellHeight(widthPerItem)
binding.carousel.layoutParams.width = widthPerItem
binding.carousel.layoutParams.height = cellHeight
binding.carousel.requestLayout()
}
return HomeBannerViewHolder(
binding
)
}
VIEW_TYPE_HOME_BOTTOM_SECTION -> {
return HomeBottomLayViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_screen_bottom_layout,
parent,
false
)
)
}
else -> {
val categoryViewHolder = CategoryViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.home_categories_item,
parent,
false
)
)
categoryViewHolder.initRecView()
return categoryViewHolder
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
VIEW_TYPE_CATEGORY -> {
(holder as CategoryViewHolder).bind(position)
}
VIEW_TYPE_ITEM_SECTION -> {
(holder as HomeSectionsViewHolder).bind(position)
}
VIEW_TYPE_BRAND -> {
(holder as BrandsViewHolder).bind(position)
}
//
VIEW_TYPE_IMAGE -> {
(holder as ImagesViewHolder).bind(position)
}
//
VIEW_TYPE_ADVANCE_IMAGE -> {
(holder as AdvanceImagesViewHolder).bind(position)
}
VIEW_TYPE_ADVANCE_CATEGORY -> {
(holder as CategorySectionViewHolder).bind(position)
}
VIEW_TYPE_BANNER -> {
// (holder as HomeBannerViewHolder).setIsRecyclable(false)
(holder as HomeBannerViewHolder).bind()
}
VIEW_TYPE_HOME_BOTTOM_SECTION -> {
(holder as HomeBottomLayViewHolder)
}
}
// holder.itemView.setFadeAnimation()
}
override fun getItemViewType(position: Int): Int {
return when (currentList[position].sectionType) {
SectionType.items -> {
VIEW_TYPE_ITEM_SECTION
}
SectionType.brands -> {
VIEW_TYPE_BRAND
}
SectionType.images -> {
VIEW_TYPE_IMAGE
}
SectionType.advanceimages -> {
VIEW_TYPE_ADVANCE_IMAGE
}
SectionType.category -> {
VIEW_TYPE_ADVANCE_CATEGORY
}
SectionType.banners -> {
VIEW_TYPE_BANNER
}
SectionType.homeCategories -> {
VIEW_TYPE_CATEGORY
}
SectionType.homeBottomView -> {
VIEW_TYPE_HOME_BOTTOM_SECTION
}
else -> {
VIEW_TYPE_CATEGORY
}
}
}
inner class CategoryViewHolder(private val itemBinding: HomeCategoriesItemBinding) :
RecyclerView.ViewHolder(
itemBinding.root
) {
fun initRecView() {
itemBinding.rvCategoryItem.adapter = categoryAdapter
}
fun bind(position: Int) {
// val newItem = (currentList[position] as CategorySection).items
// categoryAdapter.submitList(newItem)
}
}
inner class HomeSectionsViewHolder(private val itemBinding: HomeItemSectionBinding) :
RecyclerView.ViewHolder(
itemBinding.root
) {
private var sectionsAdapter = ItemSectionsAdapter(context)
fun initRecView() {
itemBinding.itemsList.adapter = sectionsAdapter
}
fun bind(position: Int) {
val section = getItem(position)
itemBinding.section = section
itemBinding.executePendingBindings()
sectionsAdapter.submitList(null)
val newItem = (currentList[position] as ItemSection).items
sectionsAdapter.submitList(newItem)
// val state = scrollStates[key]
// if (state != null) {
// itemBinding.rvProducts.layoutManager?.onRestoreInstanceState(state)
// } else {
// itemBinding.rvProducts.layoutManager?.scrollToPosition(0)
// }
}
}
inner class BrandsViewHolder(private val itemBinding: HomeBrandsItemBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
private val brandsAdapter = BrandsAdapter()
fun initRecView() {
itemBinding.itemsList.adapter = brandsAdapter
}
fun bind(position: Int) {
itemBinding.sectionBtnViewAll.visibility = View.INVISIBLE
val section = getItem(position)
itemBinding.section = section
itemBinding.executePendingBindings()
brandsAdapter.submitList((currentList[position] as BrandSection).items)
}
}
inner class ImagesViewHolder(private val itemBinding: HomeBrandsItemBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
private val imagesAdapter = ImagesAdapter(context)
val layoutManager = GridLayoutManager(context, 3)
fun initRecView() {
itemBinding.itemsList.let {
it.layoutManager = layoutManager
it.setHasFixedSize(true)
it.adapter = imagesAdapter
}
}
fun bind(position: Int) {
val numberOfColumns = getNumberOfColumns((currentList[position] as ImageSection).items)
itemBinding.itemsList.layoutManager = GridLayoutManager(context, numberOfColumns)
// layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
// override fun getSpanSize(position: Int): Int {
// return numberOfColumns
// }
// }
itemBinding.sectionBtnViewAll.visibility = View.INVISIBLE
val section = getItem(position)
itemBinding.section = section
itemBinding.executePendingBindings()
imagesAdapter.submitList((currentList[position] as ImageSection).items)
}
}
inner class AdvanceImagesViewHolder(private val itemBinding: HomeBrandsItemBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
private var imagesAdapter = AdvanceImagesAdapter(context, null, null)
fun initRecView() {
itemBinding.itemsList.let {
// val layoutManager = GridLayoutManager(context, getNumberOfColumns((currentList[position] as AdvanceImageSection).items))
val layoutManager = GridLayoutManager(context, 3)
it.layoutManager = layoutManager
it.setHasFixedSize(true)
it.adapter = imagesAdapter
}
}
fun bind(position: Int) {
val layoutManager = GridLayoutManager(
context,
getNumberOfColumns((currentList[position] as AdvanceImageSection).items)
)
itemBinding.itemsList.layoutManager = layoutManager
val section = getItem(position)
// var imagesAdapter = AdvanceImagesAdapter(context, section.columns, section.aspectRatio)
//
// itemBinding.itemsList.adapter = imagesAdapter
// val imagesAdapter = AdvanceImagesAdapter(section,context)
itemBinding.sectionBtnViewAll.visibility = View.INVISIBLE
itemBinding.section = section
itemBinding.executePendingBindings()
imagesAdapter.submitList((currentList[position] as AdvanceImageSection).items)
//
//
//
}
}
inner class CategorySectionViewHolder(private val itemBinding: HomeItemSectionBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
private val categoryAdapter = HomeCategorySectionAdapter(context)
fun initRecView() {
itemBinding.itemsList.let {
val linearLayoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
it.layoutManager = linearLayoutManager
// it.setHasFixedSize(true)
it.adapter = categoryAdapter
}
}
fun bind(position: Int) {
// (currentList[position] as CategorySection)
val section = getItem(position)
itemBinding.section = section
itemBinding.executePendingBindings()
if (categoryAdapter.itemCount <= 0) {
itemBinding.pb.show()
GlobalScope.launch(Dispatchers.Main) {
val items = async(Dispatchers.IO) {
homeRepository.getSolarSearch(
getSolarSearchRequest(section)
)
}
when (items.await()) {
is Resource.Success -> {
itemBinding.pb.hide()
categoryAdapter.submitList((items.await() as Resource.Success<SolarSearchResponse>).value.items)
}
is Resource.Failure -> {
itemBinding.pb.hide()
}
is Resource.Loading -> {
}
}
}
}
// itemBinding.sectionBtnViewAll.setOnClickListener {
// homeSectionListener.onCatSectionViewAllClicked(section.categoryID.toString(),section)
// }
}
private fun getSolarSearchRequest(section: Section): SolarSearchRequest {
val result = section.categoryID!! % 100
if (result == 0) {
return SolarSearchRequest(
0,
null,
null,
25,
"popularity",
arrayListOf(section.categoryID),
emptyList<Int>(),
selectedCondition,
selectedBrand,
"1",
"15000"
)
// putIntegerArrayListExtra(CATEGORY_ID, arrayListOf(id))
} else {
// putIntegerArrayListExtra(SUB_CAT_ID, arrayListOf(id))
return SolarSearchRequest(
OFFSET,
null,
null,
PAGE_SIZE_LIMIT,
"popularity",
emptyList<Int>(),
arrayListOf(section.categoryID),
selectedCondition,
selectedBrand,
"1",
"15000"
)
}
}
}
inner class HomeBannerViewHolder(private val itemBinding: HomeBannerItemBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
// private val handler: Handler? = null
// private val delay = 5000 //milliseconds
// private var page = 0
fun bind() {
// if (carousalListBinded) {
// return
// }
itemBinding.carousel.start()
carousalList?.let {
itemBinding.carousel.addData(it)
}
if (COVIDMsg.isNullOrEmpty()) {
itemBinding.covidMessage.visibility = View.GONE
} else {
itemBinding.covidMessage.visibility = View.VISIBLE
}
itemBinding.covidMessage.text = COVIDMsg
if (COVIDMsgColor != null) {
(itemBinding.covidMessage.background as GradientDrawable).setColor(
Color.parseColor(
COVIDMsgColor
)
)
} else {
(itemBinding.covidMessage.background as GradientDrawable).setColor(
context.getColor(
R.color.colorAccent
)
)
}
carousel = itemBinding.carousel
tvCovidMessage = itemBinding.covidMessage
if (carousalList != null) {
carousalListBinded = true
}
}
fun openNewTabWindow(urls: String, context: Context) {
val uris = Uri.parse(urls)
val intents = Intent(Intent.ACTION_VIEW, uris)
val b = Bundle()
b.putBoolean("new_window", true)
intents.putExtras(b)
context.startActivity(intents)
}
}
inner class HomeBottomLayViewHolder(private val itemBinding: HomeScreenBottomLayoutBinding) :
RecyclerView.ViewHolder(
itemBinding.root
) {
}
fun getNumberOfColumns(items: List<Image>): Int {
var numberItemsInColumn: Int = 3
if ((items.size) % 3 == 0) {
numberItemsInColumn = 3
} else if ((items.size) % 2 == 0) {
numberItemsInColumn = 2
} else if (items.size == 1) {
numberItemsInColumn = 1
} else {
numberItemsInColumn = 3
}
return numberItemsInColumn
}
#RequiresApi(Build.VERSION_CODES.M)
fun setBanners(
carousalList: List<CarouselItem>?,
bannersList: List<Banner>?,
COVIDMsg: String?,
COVIDMsgColor: String?
) {
this.carousalList = carousalList
this.banners = bannersList
this.COVIDMsg = COVIDMsg
this.COVIDMsgColor = COVIDMsgColor
carousalList?.let { carousel?.addData(it) }
if (COVIDMsg.isNullOrEmpty()) {
tvCovidMessage?.visibility = View.GONE
} else {
tvCovidMessage?.visibility = View.VISIBLE
}
tvCovidMessage?.text = COVIDMsg
if (COVIDMsgColor != null && tvCovidMessage != null) {
(tvCovidMessage?.background as GradientDrawable).setColor(Color.parseColor(COVIDMsgColor))
} else {
if (tvCovidMessage != null) {
(tvCovidMessage?.background as GradientDrawable).setColor(context.getColor(R.color.colorAccent))
}
}
}
fun getWidth(context: Context): Int {
var width: Int = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val displayMetrics = DisplayMetrics()
val display: Display? = context.display
display!!.getRealMetrics(displayMetrics)
return displayMetrics.widthPixels
} else {
val displayMetrics = DisplayMetrics()
(context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
width = displayMetrics.widthPixels
return width
}
}
fun getCellHeight(width: Int): Int {
var height = width
val ratio = 0.37
val height1 = height * ratio
height = height1.roundToInt()
return height
}
fun setCategories(categoriesList: List<Category>?) {
categoryAdapter.submitList(categoriesList)
}
}
Related
I have 4 different "Card List", "Card Magazine" , "Title" and "Grid" and view holders for each one to relate check my other question here.
now I am trying to change the layout automatically when the device rotates so when orientation is a portrait the layout be LinearLayout "Card layout" and when orientation changes to landscape the layout will be GridLayout, also I have a changeAndSaveLayout method to make the user choose between each layout from option menu
and I save the layout in ViewModel using DataStore and Flow,
The problem
When I rotate the device the RecyclerView and the list is gone and I see the empty screen,
and when I back to portrait the list is back it's back to default layout "cardLayout"
I tried multiple methods like notifyDataSetChanged after changing layout and handle changes in onConfigurationChanged methods but all these methods fails
DataStore class code saveRecyclerViewLayout and readRecyclerViewLayout
private val Context.dataStore by preferencesDataStore("user_preferences")
private const val TAG = "DataStoreRepository"
#ActivityRetainedScoped
class DataStoreRepository #Inject constructor(#ApplicationContext private val context: Context) {
suspend fun saveRecyclerViewLayout(
recyclerViewLayout: String,
) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_LAYOUT_KEY] = recyclerViewLayout
}
}
val readRecyclerViewLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_LAYOUT_KEY] ?: "cardLayout"
recyclerViewLayout
}
}
I used it in ViewModel like the following
#HiltViewModel
class PostViewModel #Inject constructor(
private val mainRepository: MainRepository,
private val dataStoreRepository: DataStoreRepository,
application: Application
) :
AndroidViewModel(application) {
val recyclerViewLayout = dataStoreRepository.readRecyclerViewLayout.asLiveData()
fun saveRecyclerViewLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLayout(layout)
}
}
}
PostAdapter Class
class PostAdapter(
private val titleAndGridLayout: TitleAndGridLayout,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var viewType = 0
private val differCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return (oldItem.id == newItem.id)
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return (oldItem == newItem)
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (this.viewType) {
CARD -> {
fromCardViewHolder(parent)
}
CARD_MAGAZINE -> {
fromCardMagazineViewHolder(parent)
}
TITLE -> {
fromTitleViewHolder(parent)
}
else -> {
fromGridViewHolder(parent)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item: Item = differ.currentList[position]
when (this.viewType) {
CARD -> if (holder is CardViewHolder) {
holder.bind(item)
}
CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
holder.bind(item)
}
TITLE -> if (holder is TitleViewHolder) {
holder.bind(item)
if (position == itemCount - 1)
titleAndGridLayout.tellFragmentToGetItems()
}
GRID -> if (holder is GridViewHolder) {
holder.bind(item)
if (position == itemCount - 1)
titleAndGridLayout.tellFragmentToGetItems()
}
}
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
class CardViewHolder(private val cardLayoutBinding: CardLayoutBinding) :
RecyclerView.ViewHolder(cardLayoutBinding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
var date: Date? = Date()
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
cardLayoutBinding.postTitle.text = item.title
try {
Glide.with(cardLayoutBinding.root).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(cardLayoutBinding.postImage)
} catch (e: IndexOutOfBoundsException) {
cardLayoutBinding.postImage.setImageResource(R.drawable.no_image)
}
cardLayoutBinding.postDescription.text = document.text()
try {
date = format.parse(item.published)
} catch (e: ParseException) {
e.printStackTrace()
}
val prettyTime = PrettyTime()
cardLayoutBinding.postDate.text = prettyTime.format(date)
}
}
class CardMagazineViewHolder(private val cardMagazineBinding: CardMagazineBinding) :
RecyclerView.ViewHolder(cardMagazineBinding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
var date: Date? = Date()
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
cardMagazineBinding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(cardMagazineBinding.postImage)
} catch (e: IndexOutOfBoundsException) {
cardMagazineBinding.postImage.setImageResource(R.drawable.no_image)
}
try {
date = format.parse(item.published)
} catch (e: ParseException) {
e.printStackTrace()
}
val prettyTime = PrettyTime()
cardMagazineBinding.postDate.text = prettyTime.format(date)
}
}
class TitleViewHolder(private val binding: TitleLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
binding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(binding.postImage)
} catch (e: IndexOutOfBoundsException) {
binding.postImage.setImageResource(R.drawable.no_image)
}
}
}
class GridViewHolder constructor(private val binding: GridLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
binding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(binding.postImage)
} catch (e: IndexOutOfBoundsException) {
binding.postImage.setImageResource(R.drawable.no_image)
}
}
}
companion object {
private const val CARD = 0
private const val CARD_MAGAZINE = 1
private const val TITLE = 2
private const val GRID = 3
private const val TAG = "POST_ADAPTER"
fun fromCardViewHolder(parent: ViewGroup): CardViewHolder {
val cardLayoutBinding: CardLayoutBinding =
CardLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(cardLayoutBinding)
}
fun fromCardMagazineViewHolder(parent: ViewGroup): CardMagazineViewHolder {
val cardMagazineBinding: CardMagazineBinding =
CardMagazineBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return CardMagazineViewHolder(cardMagazineBinding)
}
fun fromTitleViewHolder(parent: ViewGroup): TitleViewHolder {
val titleLayoutBinding: TitleLayoutBinding =
TitleLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TitleViewHolder(titleLayoutBinding)
}
fun fromGridViewHolder(
parent: ViewGroup
): GridViewHolder {
val gridLayoutBinding: GridLayoutBinding =
GridLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GridViewHolder(gridLayoutBinding)
}
}
init {
setHasStableIds(true)
}
}
and finally the HomeFragment
#AndroidEntryPoint
class HomeFragment : Fragment(), TitleAndGridLayout, MenuProvider {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private var itemArrayList = arrayListOf<Item>()
private var searchItemList = arrayListOf<Item>()
private val postViewModel: PostViewModel by viewModels()
private var linearLayoutManager: LinearLayoutManager? = null
private val titleLayoutManager: GridLayoutManager by lazy {
GridLayoutManager(requireContext(), 2)
}
private val gridLayoutManager: GridLayoutManager by lazy {
GridLayoutManager(requireContext(), 3)
}
private var menuHost: MenuHost? = null
private lateinit var networkListener: NetworkListener
private lateinit var adapter:PostAdapter
private var isScrolling = false
var currentItems = 0
var totalItems: Int = 0
var scrollOutItems: Int = 0
private var postsAPiFlag = false
private val recyclerStateKey = "recycler_state"
private val mBundleRecyclerViewState by lazy { Bundle() }
private var keyword: String? = null
private var orientation: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postViewModel.finalURL.value = "$BASE_URL?key=$API_KEY"
networkListener = NetworkListener()
}
// This property is only valid between onCreateView and
// onDestroyView.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
adapter = PostAdapter(this)
orientation = resources.configuration.orientation
_binding = FragmentHomeBinding.inflate(inflater, container, false)
menuHost = requireActivity()
menuHost?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)
postViewModel.recyclerViewLayout.observe(viewLifecycleOwner) { layout ->
linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
Log.w(TAG, "getSavedLayout called")
Log.w(TAG, "getSavedLayout: orientation ${orientation.toString()}", )
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
when (layout) {
"cardLayout" -> {
//
adapter.viewType = 0
binding.apply {
homeRecyclerView.layoutManager = linearLayoutManager
homeRecyclerView.adapter = adapter
}
}
"cardMagazineLayout" -> {
// binding.loadMoreBtn.visibility = View.VISIBLE
binding.homeRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.homeRecyclerView.adapter = adapter
}
}
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
when (layout) {
"titleLayout" -> {
// binding.loadMoreBtn.visibility = View.GONE
binding.homeRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.homeRecyclerView.adapter = adapter
}
"gridLayout" -> {
binding.homeRecyclerView.layoutManager = gridLayoutManager
adapter.viewType = 3
binding.homeRecyclerView.adapter = adapter
}
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return if (menuItem.itemId == R.id.change_layout) {
changeAndSaveLayout()
true
} else false
}
private fun changeAndSaveLayout() {
// Log.w(TAG, "changeAndSaveLayout: called")
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
val recyclerViewPortraitLayout =
resources.getStringArray(R.array.RecyclerViewPortraitLayout)
val recyclerViewLandscapeLayout =
resources.getStringArray(R.array.RecyclerViewLandscapeLayout)
// SharedPreferences.Editor editor = sharedPreferences.edit();
Log.d(TAG, "changeAndSaveLayout: ${orientation.toString()}")
if (orientation == 1) {
builder.setItems(
recyclerViewPortraitLayout
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardMagazineLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
} else if (orientation == 2) {
builder.setItems(
recyclerViewLandscapeLayout
) { _: DialogInterface?, index: Int ->
try {
when (index) {
2 -> {
adapter.viewType = 2
binding.homeRecyclerView.layoutManager = titleLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("titleLayout")
}
3 -> {
adapter.viewType = 3
binding.homeRecyclerView.layoutManager = gridLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("gridLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
}
val alertDialog = builder.create()
alertDialog.show()
}
}
GIF showing the problem
since long time I was looking for a soultion and i found it and added it to my old project, i was use shared prefernces but in your case i mean data store it will work normally
you need to create two arrays for each orintation
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="recyclerViewPortraitList">
<item>Card List</item>
<item>Card Magazine</item>
<item>Title</item>
</array>
<array name="recyclerViewLandscapeList">
<item>Grid with 3 Span</item>
<item>Grid with 4 Span</item>
</array>
</resources>
in your data store will be like the following
private object PreferencesKeys {
var RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY = stringPreferencesKey("recyclerViewPortraitLayout")
var RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY = stringPreferencesKey("recyclerViewLandscapeLayout")
}
suspend fun saveRecyclerViewPortraitLayout(
recyclerViewLayout: String,
) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY] = recyclerViewLayout
}
}
suspend fun saveRecyclerViewLandscapeLayout(recyclerViewLayout: String) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY] = recyclerViewLayout
}
}
val readRecyclerViewPortraitLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY] ?: "cardLayout"
recyclerViewLayout
}
val readRecyclerViewLandscpaeLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY] ?: "gridWith3Span"
recyclerViewLayout
}
in the viewModel
val readRecyclerViewPortraitLayout =
dataStoreRepository.readRecyclerViewPortraitLayout.asLiveData()
val readRecyclerViewLandscapeLayout =
dataStoreRepository.readRecyclerViewLandscpaeLayout.asLiveData()
fun saveRecyclerViewPortraitLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewPortraitLayout(layout)
}
}
fun saveRecyclerViewLandscapeLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLandscapeLayout(layout)
}
}
in adapter change constants
companion object {
private const val CARD = 0
private const val CARD_MAGAZINE = 1
private const val TITLE = 2
private const val GRID_WITH_3_SPAN = 3
private const val GRID_WITH_4_SPAN = 4
}
5.and finally in the fragment or activity you can use it like the following
private fun setUpRecyclerViewLayout() {
if (requireActivity().resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
postViewModel.readRecyclerViewPortraitLayout.observe(viewLifecycleOwner) { layout ->
recyclerViewLayout = layout
when (layout) {
"cardLayout" -> {
adapter.viewType = 0
binding.apply {
homeRecyclerView.layoutManager = linearLayoutManager
homeRecyclerView.adapter = adapter
homeRecyclerView.setHasFixedSize(true)
}
}
"cardMagazineLayout" -> {
binding.homeRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.homeRecyclerView.adapter = adapter
}
"titleLayout" -> {
binding.homeRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.homeRecyclerView.adapter = adapter
}
}
}
} else {
postViewModel.readRecyclerViewLandscapeLayout.observe(viewLifecycleOwner) { layout ->
recyclerViewLayout = layout
when (layout) {
"gridWith3Span" -> {
binding.homeRecyclerView.layoutManager = gridWith3SpanLayoutManager
adapter.viewType = 3
binding.homeRecyclerView.adapter = adapter
}
"gridWith4Span" -> {
binding.homeRecyclerView.layoutManager = gridWith4SpanLayoutManager
adapter.viewType = 4
binding.homeRecyclerView.adapter = adapter
}
}
}
}
}
private fun changeAndSaveLayout() {
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
// SharedPreferences.Editor editor = sharedPreferences.edit();
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
builder.setItems(
resources.getStringArray(R.array.recyclerViewPortraitList)
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("cardMagazineLayout")
}
2 -> {
adapter.viewType = 2
binding.homeRecyclerView.layoutManager = titleLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("titleLayout")
}
else -> {
throw Exception("Unknown layout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
} else {
builder.setItems(
resources.getStringArray(R.array.recyclerViewLandscapeList)
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 3
binding.homeRecyclerView.layoutManager = gridWith3SpanLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLandscapeLayout("gridWith3Span")
}
1 -> {
adapter.viewType = 4
binding.homeRecyclerView.layoutManager = gridWith4SpanLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLandscapeLayout("gridWith4Span")
}
else -> {
throw Exception("Unknown layout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
}
val alertDialog = builder.create()
alertDialog.show()
}
if you setting configuration changes in manifest like that android:configChanges="orientation|screenSize"you will need to call the setUpRecyclerViewLayout() from onConfigurationChanged
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
setUpRecyclerViewLayout()
}
I'm currently following a news app tutorial and I have a problem. When I type in a keyword in the edit text widget, articles related to that keyword shows up in the recycler view but when I erase that keyword to type in another keyword, the articles (in the recycler view) from the previous search query doesn't update and even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing. Can anyone please take a look at my code and let me know what I've done wrong. Thanks in advance.
Here is my code:
Search Fragment
`class SearchNewsFragment : Fragment(R.layout.fragment_search_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
val TAG = "SearchNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
setupRecyclerView()
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_searchNewsFragment_to_articleFragment,
bundle
)
}
var job: Job? = null
etSearch.addTextChangedListener { editable ->
job?.cancel()
job = MainScope().launch {
delay(SEARCH_NEWS_TIME_DELAY)
editable?.let {
if(editable.toString().isNotEmpty()) {
viewModel.searchNews(editable.toString())
}
}
}
}
viewModel.searchNews.observe(viewLifecycleOwner, Observer { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
hideErrorMessage()
response.data?.let { newsResponse ->
newsAdapter.differ.submitList(newsResponse.articles.toList())
val totalPages = newsResponse.totalResults / Constants.QUERY_PAGE_SIZE + 2
isLastPage = viewModel.searchNewsPage == totalPages
if(isLastPage) {
rvSearchNews.setPadding(0, 0, 0, 0)
}
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Toast.makeText(activity, "An error occured: $message", Toast.LENGTH_LONG).show()
showErrorMessage(message)
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
btnRetry.setOnClickListener {
if (etSearch.text.toString().isNotEmpty()) {
viewModel.searchNews(etSearch.text.toString())
} else {
hideErrorMessage()
}
}
}
private fun hideProgressBar() {
paginationProgressBar.visibility = View.INVISIBLE
isLoading = false
}
private fun showProgressBar() {
paginationProgressBar.visibility = View.VISIBLE
isLoading = true
}
private fun hideErrorMessage() {
itemErrorMessage.visibility = View.INVISIBLE
isError = false
}
private fun showErrorMessage(message: String) {
itemErrorMessage.visibility = View.VISIBLE
tvErrorMessage.text = message
isError = true
}
var isError = false
var isLoading = false
var isLastPage = false
var isScrolling = false
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val isNoErrors = !isError
val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
val isNotAtBeginning = firstVisibleItemPosition >= 0
val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE
val shouldPaginate = isNoErrors && isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning &&
isTotalMoreThanVisible && isScrolling
if(shouldPaginate) {
viewModel.searchNews(etSearch.text.toString())
isScrolling = false
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if(newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
}
private fun setupRecyclerView() {
newsAdapter = NewsAdapter()
rvSearchNews.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
addOnScrollListener(this#SearchNewsFragment.scrollListener)
}
}
}`
SearchNewsAdapter
`class SearchNewsAdapter : RecyclerView.Adapter<SearchNewsAdapter.ArticleViewHolder>() {
// Inner class for viewHolder
inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<Article>(){
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url== newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
//recyclerViewFunction
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.search_article_preview,parent, false)
)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article = differ.currentList[position]
holder.itemView.apply{
// Glide.with(this).load(article.urlToImage).into(ivArticleImage)
searchTitle.text = article.title
setOnClickListener{
onItemClickListener?.let{
it(article)
}
}
}
}
//item click listener to single article so that article fragment opens up the webview that shows our items
private var onItemClickListener: ((Article) -> Unit)? = null
fun setOnItemClickListener(listener:(Article) -> Unit){
onItemClickListener = listener
}
}`
NewsViewModel
`class NewsViewModel(
app: Application,
val newsRepository: NewsRepository
) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var newSearchQuery:String? = null
var oldSearchQuery:String? = null
init {
getBreakingNews("us")
}
fun getBreakingNews(countryCode: String) = viewModelScope.launch {
safeBreakingNewsCall(countryCode)
}
fun searchNews(searchQuery: String) = viewModelScope.launch {
safeSearchNewsCall(searchQuery)
}
private fun handleBreakingNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if(breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
private fun handleSearchNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
if(searchNewsResponse == null || newSearchQuery != oldSearchQuery) {
searchNewsPage = 1
oldSearchQuery = newSearchQuery
searchNewsResponse = resultResponse
} else {
searchNewsPage++
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
fun getSavedNews() = newsRepository.getSavedNews()
fun deleteArticle(article: Article) = viewModelScope.launch {
newsRepository.deleteArticle(article)
}
private suspend fun safeSearchNewsCall(searchQuery: String) {
newSearchQuery = searchQuery
searchNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue(handleSearchNewsResponse(response))
} else {
searchNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> searchNews.postValue(Resource.Error("Network Failure"))
else -> searchNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private suspend fun safeBreakingNewsCall(countryCode: String) {
breakingNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
} else {
breakingNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> breakingNews.postValue(Resource.Error("Network Failure"))
else -> breakingNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private fun hasInternetConnection(): Boolean {
val connectivityManager = getApplication<NewsApplication>().getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
return when {
capabilities.hasTransport(TRANSPORT_WIFI) -> true
capabilities.hasTransport(TRANSPORT_CELLULAR) -> true
capabilities.hasTransport(TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.activeNetworkInfo?.run {
return when(type) {
TYPE_WIFI -> true
TYPE_MOBILE -> true
TYPE_ETHERNET -> true
else -> false
}
}
}
return false
}
}
`
if(editable.toString().isNotEmpty()) { viewModel.searchNews(editable.toString()) }
this line of code prevent the empty query to be processed. So, when you delete everything from the edit text, it will do nothing, hence the result still the same.
even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing.
The search result is strored on NewsViewModel and because the ViewModel is initialized on the NewsActivity, it tied to the activity lifecycle. Even if you destroy the fragment, the search result (the whole ViewModel) will be kept because the activity is still alive. So, when you open back the search Fragment, the LiveData will give you the latest value.
I have a recycler view with multiview types (For Incoming and Outgoing) messages and a static list which I am passing to the adapter to test my UI. My goal is to sort view holders' messages w.r.t data like we have in whatsapp. I have a separate viewholder for date as well.
Custom Object
class ChatTempModel(val viewType: Int, val senderName : String, val date: Long, val message : String, val clinicalList: ArrayList<ClinicalTeam>? )
This is my Chat Adapter Code:
class ChatAdapter(private val context: Context, private var list: ArrayList<ChatTempModel>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var clinicalTeamAdapter: ClinicalTeamAdapter
companion object {
const val VIEW_TYPE_FIRST = 12
const val VIEW_TYPE_DEFAULT_CHAT = 0 //Default Static Text
const val VIEW_TYPE_INCOMING_CHAT = 1 //Incoming
const val VIEW_TYPE_OUTGOING_CHAT = 2 //Outgoing
const val VIEW_TYPE_PDF_UPLOAD = 3 //OutgoingPDF
const val VIEW_TYPE_IMAGE_UPLOAD = 4 //Outgoing Image
const val VIEW_TYPE_DATE = 5
const val VIEW_TYPE_PDF_INCOMING = 6
const val VIEW_TYPE_IMAGE_INCOMING = 7
}
private inner class FirstChatViewHolder(private val binding: ItemFirstMessageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
tvSenderName.text = recyclerViewModel.senderName
tvInComingMessage.text = recyclerViewModel.message
// tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
}
}
}
private inner class DefaultChatViewHolder(private val binding: ItemDefaultChatViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
tvInComingMessage.text = recyclerViewModel.message
//tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
clinicalTeamAdapter = ClinicalTeamAdapter(context)
clinicalTeamAdapter.submitList(recyclerViewModel.clinicalList)
rvClinicalTeam.adapter = clinicalTeamAdapter
}
}
}
private inner class IncomingChatViewHolder(private val binding: ItemIncomingChatBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
tvInComingMessage.text = recyclerViewModel.message
//tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
}
}
}
private inner class OutgoingChatViewHolder(private val binding: ItemOutgoingChatBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
tvInComingMessage.text = recyclerViewModel.message
// tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
}
}
}
private inner class PDFChatViewHolder(private val binding: ItemPdfUploadViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
// tvInComingMessage.text = recyclerViewModel.message
// tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
tvPdfName.text = "Jack's Blood Test.pdf"
}
}
}
private inner class ImageUploadViewHolder(private val binding: ItemImageUploadViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
//tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
context.loadImage(recyclerViewModel.message, binding.ivUploadedImage)
ivUploadedImage.setOnClickListener{
val b = Bundle()
b.putString("abc",recyclerViewModel.message)
val navController = context.let { Navigation.findNavController(it as Activity, R.id.nav_host_fragment_dashboard) }
navController.navigate(R.id.fullScreenImageFragment,b)
}
}
}
}
private inner class PDFIncomingViewHolder(private val binding: ItemPdfIncomingViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
// tvInComingMessage.text = recyclerViewModel.message
// tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
tvPdfName.text = recyclerViewModel.message
}
}
}
private inner class ImageIncomingViewHolder(private val binding: ItemImageIncomingViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
// tvTime.text = recyclerViewModel.date.toString()
tvTime.text = getDateTime(recyclerViewModel.date)
tvSenderName.text = recyclerViewModel.senderName
context.loadImage(recyclerViewModel.message, binding.ivUploadedImage)
ivUploadedImage.setOnClickListener{
val b = Bundle()
b.putString("abc",recyclerViewModel.message)
val navController = context.let { Navigation.findNavController(it as Activity, R.id.nav_host_fragment_dashboard) }
navController.navigate(R.id.fullScreenImageFragment,b)
}
}
}
}
private inner class DateViewHolder(private val binding: ItemDateViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
val recyclerViewModel = list[position]
binding.apply {
// tvDate.text = getDateTime(recyclerViewModel.date)
// tvDate.text = "30 Sep, 2021"
tvDate.text = getDate(recyclerViewModel.date)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == VIEW_TYPE_DEFAULT_CHAT) {
return DefaultChatViewHolder(
ItemDefaultChatViewBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
} else if (viewType == VIEW_TYPE_INCOMING_CHAT) {
return IncomingChatViewHolder(
ItemIncomingChatBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else if (viewType == VIEW_TYPE_OUTGOING_CHAT) {
return OutgoingChatViewHolder(
ItemOutgoingChatBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else if (viewType == VIEW_TYPE_PDF_UPLOAD) {
return PDFChatViewHolder(
ItemPdfUploadViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else if (viewType == VIEW_TYPE_IMAGE_UPLOAD) {
return ImageUploadViewHolder(
ItemImageUploadViewBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
} else if (viewType == VIEW_TYPE_DATE) {
return DateViewHolder(
ItemDateViewBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else if(viewType == VIEW_TYPE_PDF_INCOMING){
return PDFIncomingViewHolder(ItemPdfIncomingViewBinding.inflate(LayoutInflater.from(parent.context), parent,false))
} else if(viewType == VIEW_TYPE_IMAGE_INCOMING){
return ImageIncomingViewHolder(ItemImageIncomingViewBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
else {
return FirstChatViewHolder(
ItemFirstMessageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (list[position].viewType == VIEW_TYPE_DEFAULT_CHAT) {
(holder as DefaultChatViewHolder).bind(position)
} else if (list[position].viewType === VIEW_TYPE_INCOMING_CHAT) {
(holder as IncomingChatViewHolder).bind(position)
} else if (list[position].viewType == VIEW_TYPE_OUTGOING_CHAT) {
(holder as OutgoingChatViewHolder).bind(position)
} else if (list[position].viewType == VIEW_TYPE_PDF_UPLOAD) {
(holder as PDFChatViewHolder).bind(position)
} else if (list[position].viewType == VIEW_TYPE_IMAGE_UPLOAD) {
(holder as ImageUploadViewHolder).bind(position)
} else if (list[position].viewType == VIEW_TYPE_DATE) {
(holder as DateViewHolder).bind(position)
} else if(list[position].viewType == VIEW_TYPE_PDF_INCOMING){
(holder as PDFIncomingViewHolder).bind(position)
} else if(list[position].viewType == VIEW_TYPE_IMAGE_INCOMING){
(holder as ImageIncomingViewHolder).bind(position)
}
else {
(holder as FirstChatViewHolder).bind(position)
}
}
override fun getItemCount(): Int {
return list.size
}
}
Goal is to show date view holder and sort message like WhatsApp.
I am am trying to put filter in expandable recylerview but not able to achieve:
Please find the code below:
class adapter1(internal var context: Context, internal var mData: MutableList<faqBody?>,val
fragmentInterface: FragmentInterface) : RecyclerView.Adapter<adapter1.myViewHolder>()
,Filterable {
internal var mfilter: NewFilter
var orginallist: MutableList<faqBody?> = ArrayList()
override fun getFilter(): Filter {
return mfilter
}
init {
mfilter = NewFilter(this#adapter1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): adapter1.myViewHolder {
val view =
LayoutInflater.from(context).inflate(R.layout.recyclerview_adapter12, parent, false)
return myViewHolder(view)
}
override fun onBindViewHolder(holder: adapter1.myViewHolder, position: Int) {
val countryDataItem = mData[position]
val intMaxNoOfChild = 0
holder.country.text = countryDataItem!!.menuText
val noOfChildTextViews = holder.linearLayout_childItems.childCount
val noOfChild = countryDataItem.childItem.size
for (index in 0 until noOfChildTextViews) {
val currentTextView =
holder.linearLayout_childItems.getChildAt(index) as TextView
currentTextView.visibility = View.VISIBLE
}
if (noOfChild < noOfChildTextViews) {
for (index in noOfChild until noOfChildTextViews) {
val currentTextView =
holder.linearLayout_childItems.getChildAt(index) as TextView
currentTextView.visibility = View.GONE
}
}
for (textViewIndex in 0 until noOfChild) {
val currentTextView =
holder.linearLayout_childItems.getChildAt(textViewIndex) as TextView
currentTextView.setText(countryDataItem.childItem[textViewIndex].menuText)
}
}
override fun getItemCount(): Int {
return mData.size
}
inner class myViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
internal var country: TextView
val linearLayout_childItems: LinearLayout
init {
country = itemView.findViewById(R.id.country)
linearLayout_childItems = itemView.findViewById(R.id.ll_child_items)
linearLayout_childItems.visibility = View.GONE
var intMaxNoOfChild = 0
for (index in mData.indices) {
val intMaxSizeTemp = mData[index]!!.childItem.size
if (intMaxSizeTemp > intMaxNoOfChild) intMaxNoOfChild = intMaxSizeTemp
}
for (indexView in 0 until intMaxNoOfChild) {
val textView = TextView(context)
textView.id = indexView
textView.setPadding(5, 20, 0, 20)
// textView.background = ContextCompat.getDrawable(context, R.drawable.background_sub_module_text)
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
textView.setOnClickListener(this)
linearLayout_childItems.addView(textView, layoutParams)
}
country.setOnClickListener(this)
}
override fun onClick(view: View) {
if (view.getId() == R.id.country) {
if (linearLayout_childItems.visibility == View.VISIBLE) {
linearLayout_childItems.visibility = View.GONE
} else {
linearLayout_childItems.visibility = View.VISIBLE
}
} else {
val textViewClicked = view as TextView
Toast.makeText(
context, "" + textViewClicked.text.toString(), Toast.LENGTH_SHORT
).show()
fragmentInterface.onClick(textViewClicked.text.toString())
}
}
}
inner class NewFilter(var mAdapter: adapter1) : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
orginallist.clear()
orginallist.addAll(mData)
val results = FilterResults()
if (charSequence.length == 0) {
mData.addAll(orginallist)
} else {
val filterPattern = charSequence.toString().toLowerCase().trim { it <= ' ' }
for (i in 0..orginallist.size) {
val newList: ArrayList<childItem> = ArrayList<childItem>()
for (childmenu in orginallist[i]!!.childItem) {
if (childmenu.menuText.toLowerCase().contains(filterPattern)) {
newList.add(childmenu)
}
}
val faqBody = faqBody(menuText = orginallist[i]!!.menuText, childItem = newList)
mData.add(faqBody)
}
}
results.values = mData
results.count = mData.size
return results
}
override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) {
mAdapter.notifyDataSetChanged()
}
}
}
Can anyone help on the above.
mData is the list of faqBody that contains chilitems of the same.
faqbody (
menutext: String
childItem : List<childItem>
)
chilitem(
menutext: String
)
I have two screens first one has recycler view list of data and searchView above it that's filter data in this recycler, the view Model code of the first fragment
class MscInspectionViewModel(val activity: LaunchActivity, val mRootView: MscInspectFragment) :
BaseViewModel(),
SwipeRefreshLayout.OnRefreshListener {
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
private val getDataError = MutableLiveData<Boolean>()
var listType = MutableLiveData<Int>()
val hint = MutableLiveData<String>()
private var isRefreshing: Boolean = false
private var mSharedPreferences: SharedPreferences? = null
val dataListAdapter = ContainersUnderCheckAdapter(activity)
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val filterDataByTab = object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab!!.text) {
activity.resources.getString(R.string.cidPending) -> {
listType.value = 0
getPendingData()
}
activity.resources.getString(R.string.cidDone) -> {
listType.value = 1
getDoneData()
}
}
}
}
val filterData = object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (query.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(query)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(newText)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
}
return false;
}
}
val closeImgListener = View.OnClickListener {
mRootView.svSearchMSC.setQuery("", true)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
init {
listType.value = 0
mSharedPreferences = getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = activity.resources.getString(R.string.mscInspectTitle)
hint.value = activity.resources.getString(R.string.msc_search)
getData()
}
fun getData() {
onRetrievePostListStart()
subscription = apiAccount.getContainersUnderCheck(
"getContainersUnderCheck",
mSharedPreferences!!.getString(Constants.CFID, "")!!,
mSharedPreferences!!.getString(Constants.CFTOKEN, "")!!
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {}
.doOnTerminate {}
.subscribe({ result ->
result?.let {
if (result.ResponseCode != null && result.ResponseCode.trim() != "000") {
onRetrievePostListError(result.ResponseMessage)
} else {
result.ContainersData?.let { it1 -> onRetrievePostListSuccess(it1) }
}
}
}, { throwable ->
android.util.Log.e("getDataInquiry", throwable.message!!)
onRetrievePostListError(activity.resources.getString(R.string.general_error))
})
}
private fun getPendingData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun getDoneData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
isRefreshing = false
}
private fun onRetrievePostListSuccess(containersData: List<ContainersData>) {
onRetrievePostListFinish()
dataListAdapter.updateInquiryAdapter(containersData as ArrayList<ContainersData>)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
override fun onRefresh() {
isRefreshing = true
getData()
}
}
adapter is :
class ContainersUnderCheckAdapter(val activity: LaunchActivity) :
RecyclerView.Adapter<ContainersUnderCheckAdapter.ViewHolder>() {
private lateinit var mDataSet: ArrayList<ContainersData>
private lateinit var mDataSetFiltered: ArrayList<ContainersData>
fun updateInquiryAdapter(dataSet: ArrayList<ContainersData>) {
mDataSet = ArrayList()
mDataSet.clear()
mDataSet.addAll(dataSet)
mDataSetFiltered = mDataSet
getFilter(2, 1).filter("PENDING")
// notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerItemFieldLayoutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_item_field_layout,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return if (::mDataSetFiltered.isInitialized) mDataSetFiltered.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mDataSetFiltered[position])
}
operator fun get(position: Int): ContainersData {
return mDataSetFiltered.get(position)
}
/**
* #filterType :
* IF 1 : filter on Data Type RJCTD + APPROVED
* 2 : filter on Data Type PENDING
* 3 :
*/
fun getFilter(filterType: Int, listType: Int): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val charString = charSequence.toString()
mDataSetFiltered = if (charString.isEmpty()) {
mDataSet
} else {
val filteredList = ArrayList<ContainersData>()
for (row in mDataSet) {
when (filterType) {
1 -> {
if (row.status == "RJCTD" || row.status == "APPROVED") {
filteredList.add(row)
}
}
2 -> {
if (row.status == charString) {
filteredList.add(row)
}
}
3 -> {
when (listType) {
0 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status == "PENDING"
) {
filteredList.add(row)
}
}
1 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status != "PENDING"
) {
filteredList.add(row)
}
}
}
}
}
}
filteredList
}
val filterResults = FilterResults()
filterResults.values = mDataSetFiltered
return filterResults
}
override fun publishResults(
charSequence: CharSequence,
filterResults: FilterResults
) {
if (::mDataSetFiltered.isInitialized) {
mDataSetFiltered = try {
filterResults.values as ArrayList<ContainersData>
} catch (e: Exception) {
Log.e("mDataSetFiltered",e.message!!)
ArrayList()
}
when (filterType) {
1->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p1!!.UpdateDate.compareTo(p0!!.UpdateDate) })
}
2->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p0!!.ID!!.compareTo(p1.ID!!) })
}
}
}
// refresh the list with filtered data
notifyDataSetChanged()
}
}
}
class ViewHolder(
private val binding: ContianerItemFieldLayoutBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscInspectionListViewModel(activity)
fun bind(data: ContainersData) {
viewModel.bind(data)
binding.viewModel = viewModel
}
}
}
any data in this recycler on click go to fragment has tow recycler first one to show data, the second one to pick Images
the second-page code
class MSCDataFragment : Fragment() {
lateinit var rootView: View
lateinit var activity: LaunchActivity
lateinit var utility: Utility
lateinit var loadingView: LoadingView
private lateinit var viewModel: MSCDataViewModel
private lateinit var binding: FragmentMscdataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getActivity() != null) {
activity = getActivity() as LaunchActivity
utility = Utility(activity)
loadingView = LoadingView(activity)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mscdata, container, false)
rootView = binding.root
initial()
return rootView
}
private fun initial() {
viewModel = ViewModelProvider(
this, ViewModelFactory(
activity,
arguments!!.getSerializable("Data") as ContainersData
)
).get(MSCDataViewModel::class.java)
binding.viewModel = viewModel
// binding.imgList.layoutManager = GridLayoutManager(activity, 3)
binding.containerInfo.layoutManager = LinearLayoutManager(activity)
binding.openCIDNotValid.typeface =
Typeface.createFromAsset(activity.assets, "Bahij_Janna-Regular.ttf")
binding.openCIDNotValid.setOnCheckedChangeListener(viewModel.onOpenCidNotValidListener)
viewModel.loading.observe(this, Observer { loading ->
loading?.let {
if (it) {
loadingView.show()
} else {
loadingView.dismiss()
}
}
})
viewModel.errorMessage.observe(this, Observer { msg ->
msg?.let {
utility.ShowToast(msg)
}
})
viewModel.imagesAdapters2.observe(this, Observer { msg ->
msg?.let {
binding.imgList.apply {
layoutManager = GridLayoutManager(activity, 3)
adapter = it
}
}
})
rootView.toolbar_Back.setOnClickListener(viewModel.backClickListener)
binding.btnAddImages.setOnClickListener(viewModel.pickImages)
binding.successContianer.setOnClickListener(viewModel.correctContainer)
binding.damagedContianer.setOnClickListener(viewModel.wrongContainer)
}
}
the view model is :
class MSCDataViewModel(val activity: LaunchActivity, val containersData: ContainersData) :
BaseViewModel(), GetImagesListener {
#Inject
lateinit var restApiAccount: RestApiAccount
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
val ButtonText: MutableLiveData<String> = MutableLiveData()
var openCIDNotValidVisibility = MutableLiveData<Int>()
private val getDataError = MutableLiveData<Boolean>()
val btnImagesVisibility = MutableLiveData<Int>()
var imgeNoteVisibility = MutableLiveData<Int>()
var successVisibility = MutableLiveData<Int>()
var damagedVisibility = MutableLiveData<Int>()
var notesVisibility = MutableLiveData<Int>()
val btnVisibility = MutableLiveData<Int>()
var canNotOpen = MutableLiveData<Int>()
private val images = ArrayList<Image>()
var utility = Utility(activity)
private var CURRENTINDEX = 0
private var mSharedPreferences: SharedPreferences? = null
val DataListAdapter = ContainerDataAdapter(activity)
var imagesAdapter = ContainerImagesAdapter(activity, containersData.status!!, ArrayList())
val imagesAdapters2 = MutableLiveData<ContainerImagesAdapter2>()
val userInfo: UserInfo
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val pickImages = View.OnClickListener {
pickImages()
}
val correctContainer = View.OnClickListener {}
val onOpenCidNotValidListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
successVisibility.value = View.GONE
canNotOpen.value = 1
} else {
canNotOpen.value = 0
successVisibility.value = View.VISIBLE
}
}
val wrongContainer = View.OnClickListener {}
var mscNotes: ObservableField<String> = ObservableField("")
init {
canNotOpen.value = 0
mSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = containersData.CID
ButtonText.value = activity.resources.getString(R.string.cleanContianer)
userInfo = utility.readObjectFromSharedPreferences(
mSharedPreferences,
Constants.USER_INFO_KEY,
UserInfo::class.java
) as UserInfo
openCIDNotValidVisibility.value = View.GONE
fillData()
}
private fun fillData() {
val data: LinkedHashMap<String, String> = containersData.data!!
val captionsMap = utility.readObjectFromSharedPreferences(
mSharedPreferences, Constants.CAPTIONS_MAP_KEY,
HashMap::class.java
) as HashMap<String, String>
if (containersData.data.size > 0) {
val list = ArrayList<KeyValueModel>()
for (inside in data.keys) {
val ky = captionsMap[inside]
val value = data[inside].toString()
ky?.let { KeyValueModel(it, value) }?.let { list.add(it) }
}
DataListAdapter.updateInquiryAdapter(list)
} else {
errorMessage.value = activity.resources.getString(R.string.no_data)
}
if (containersData.ImageList != null && containersData.ImageList.isNotEmpty()) {
imagesAdapter.updateContainerImagesAdapter(containersData.ImageList)
}
}
private fun pickImages() {
activity.setCallBack(this)
val pictureDialog: AlertDialog
val builder = activity.let { AlertDialog.Builder(it) }
val dialogView = View.inflate(activity, R.layout.choose_camera_method, null)
builder.setView(dialogView)
val nafithPopupContainer = dialogView.findViewById<RelativeLayout>(R.id.RLTitle)
nafithPopupContainer.setBackgroundColor(
ContextCompat.getColor(
activity,
R.color.mainColor
)
)
val popUpGallery = dialogView.findViewById<LinearLayout>(R.id.PopupGellary)
val popUpCamera = dialogView.findViewById<LinearLayout>(R.id.PopupCamera)
pictureDialog = builder.create()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Objects.requireNonNull<Window>(pictureDialog.window)
.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
if (pictureDialog.window != null) {
pictureDialog.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
popUpGallery.setOnClickListener {
fromGallery()
pictureDialog.dismiss()
}
popUpCamera.setOnClickListener {
fromCamera()
pictureDialog.dismiss()
}
val popupClose = dialogView.findViewById<ImageView>(R.id.popupClose)
popupClose.setOnClickListener { pictureDialog.dismiss() }
pictureDialog.show()
}
private fun fromGallery() {
ImagePicker.create(activity)
.toolbarImageTitle(activity.resources.getString(R.string.get_image))
.toolbarArrowColor(ContextCompat.getColor(activity, R.color.colorWhite))
.showCamera(false)
.limit(6)
.start()
}
private fun fromCamera() {
ImagePicker.cameraOnly().start(activity)
}
override fun onGetImage(image: Image) {
imgeNoteVisibility.value = View.GONE
imagesAdapter.updateContainerImagesAdapter(image)
images.add(image)
}
override fun addingImagesDone(mImages: MutableList<Image>) {
images.clear()
images.addAll(mImages)
imgeNoteVisibility.value = View.GONE
val listString :ArrayList<String> = ArrayList()
for (i in mImages.indices){
listString.add(mImages[i].path)
}
imagesAdapters2.value = ContainerImagesAdapter2(activity,containersData.status!!,listString)
imagesAdapters2.value!!.notifyItemRangeChanged(0,listString.size)
}
override fun onImgDelete(image: String) {
var x = 0
try {
for (i in 0 until images.size) {
x = i
if (images[i].path == image) {
images.remove(images[i])
}
}
} catch (e: Exception) {
Log.e("errorImages", e.message!!)
Log.e("xx", x.toString())
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
}
private fun onRetrievePostListSuccess(msg: String?) {
onRetrievePostListFinish()
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
}
Adapter code is :
class ContainerImagesAdapter2() : RecyclerView.Adapter<ContainerImagesAdapter2.ViewHolder>() {
var status: String = ""
lateinit var activity: LaunchActivity
lateinit var utility: Utility
constructor(
mActivity: LaunchActivity,
mStatus: String,
pathsList: ArrayList<String>
) : this() {
activity = mActivity
pathsDataSet = pathsList
status = mStatus
utility = Utility(activity)
}
private var pathsDataSet: ArrayList<String> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerImageFieldBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_image_field,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return pathsDataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindPath(pathsDataSet[position], position)
}
inner class ViewHolder(
private val binding: ContianerImageFieldBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscImagesListViewModel(activity)
fun bindPath(data: String, position: Int) {
viewModel.bindPath(data)
binding.viewModel = viewModel
if (status != "PENDING") {
binding.closeImg.visibility = View.GONE
}
binding.closeImg.setOnClickListener {}
binding.mainImg.setOnClickListener {
val fragment = FullImageFragment()
val bundle = Bundle()
val list = ArrayList<String>()
for (item in 0 until pathsDataSet.size) {
list.add(pathsDataSet[item])
}
bundle.putSerializable("ImageList", list)
bundle.putInt("Position", position)
fragment.arguments = bundle
activity.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).addToBackStack(fragment.tag)
.commit()
}
}
}
}
if you filter data using search view in the first-page and pick images in the second page , list of picked images doesn't appear, if you going to the second page without filtering data everything ok
solve Problem found
Just Update constraint-layout library in gradle dependencies to version '2.0.0-beta4'