Skip to content

Commit 49ef06f

Browse files
committed
Add header to RecyclerView in Search Fragment
1 parent 8cbdab6 commit 49ef06f

File tree

6 files changed

+108
-69
lines changed

6 files changed

+108
-69
lines changed

app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class HomeFragment : Fragment() {
6161
val appBarConfiguration = AppBarConfiguration(navController.graph)
6262
binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration)
6363
binding.fragmentHomeToolbar.inflateMenu(R.menu.options_menu)
64-
binding.fragmentHomeToolbar.setOnMenuItemClickListener(){
64+
binding.fragmentHomeToolbar.setOnMenuItemClickListener{
6565
binding.fragmentHomeToolbar.findNavController().navigate(R.id.action_homeFragmentDest_to_searchFragment)
6666
true
6767
}

app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ import android.widget.SearchView
1010
import androidx.fragment.app.Fragment
1111
import androidx.fragment.app.viewModels
1212
import androidx.lifecycle.lifecycleScope
13-
import androidx.navigation.findNavController
1413
import androidx.navigation.fragment.findNavController
1514
import androidx.navigation.ui.AppBarConfiguration
1615
import androidx.navigation.ui.setupWithNavController
1716
import androidx.paging.PagingData
18-
import androidx.paging.insertHeaderItem
19-
import androidx.paging.map
2017
import androidx.recyclerview.widget.LinearLayoutManager
21-
import com.intive.tmdbandroid.R
2218
import com.intive.tmdbandroid.common.State
2319
import com.intive.tmdbandroid.databinding.FragmentSearchBinding
2420
import com.intive.tmdbandroid.model.TVShow
@@ -31,7 +27,12 @@ import kotlinx.coroutines.flow.collectLatest
3127
class SearchFragment: Fragment() {
3228
private val viewModel: SearchViewModel by viewModels()
3329

34-
private val searchAdapter = TVShowSearchAdapter()
30+
private val clickListener = { tvShow: TVShow ->
31+
val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id)
32+
findNavController().navigate(action)
33+
}
34+
35+
private val searchAdapter = TVShowSearchAdapter(clickListener)
3536

3637
private var searchViewQuery: String = ""
3738

@@ -67,7 +68,7 @@ class SearchFragment: Fragment() {
6768
val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
6869
binding.searchView.postDelayed( {
6970
imm.showSoftInput(binding.searchView, InputMethodManager.SHOW_IMPLICIT)
70-
}, 100)
71+
}, 50)
7172
}
7273
else{
7374
binding.layoutSearchHint.hintContainer.visibility = View.GONE
@@ -83,12 +84,12 @@ class SearchFragment: Fragment() {
8384
}
8485

8586
fun subscribeViewModel(binding: FragmentSearchBinding){
86-
searchAdapter.addLoadStateListener { loadState ->
87-
if ( loadState.append.endOfPaginationReached ){
88-
if ( searchAdapter.itemCount < 1)
87+
searchAdapter.notifyItemChanged(0)
88+
searchAdapter.differ.addLoadStateListener { loadState ->
89+
if(loadState.append.endOfPaginationReached){
90+
if (searchAdapter.itemCount < 1 + 1) {
8991
binding.layoutEmpty.root.visibility = View.VISIBLE
90-
else
91-
binding.layoutEmpty.root.visibility = View.GONE
92+
} else binding.layoutEmpty.root.visibility = View.GONE
9293
}
9394
}
9495
lifecycleScope.launchWhenStarted {
@@ -98,8 +99,6 @@ class SearchFragment: Fragment() {
9899
is State.Success<PagingData<TVShow>> -> {
99100
binding.layoutSearchHint.hintContainer.visibility = View.GONE
100101
binding.layoutProgressbar.progressBar.visibility = View.GONE
101-
binding.searchHeaderContainer.visibility = View.VISIBLE
102-
binding.searchHeaderText.text = binding.searchHeaderText.context.getString(R.string.search_result_header, searchViewQuery)
103102
searchAdapter.submitData(resultTVShow.data)
104103
}
105104
is State.Error -> {
@@ -120,11 +119,6 @@ class SearchFragment: Fragment() {
120119
val resultsList = binding.searchResults
121120

122121
resultsList.apply {
123-
searchAdapter.clickListener = { tvShow ->
124-
val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id)
125-
findNavController().navigate(action)
126-
}
127-
128122
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
129123
adapter = searchAdapter
130124
}

app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,92 @@ package com.intive.tmdbandroid.search.ui.adapters
22

33
import android.view.LayoutInflater
44
import android.view.ViewGroup
5-
import androidx.paging.PagingDataAdapter
5+
import androidx.paging.AsyncPagingDataDiffer
6+
import androidx.paging.PagingData
7+
import androidx.recyclerview.widget.AdapterListUpdateCallback
68
import androidx.recyclerview.widget.DiffUtil
9+
import androidx.recyclerview.widget.ListUpdateCallback
710
import androidx.recyclerview.widget.RecyclerView
811
import com.bumptech.glide.Glide
912
import com.bumptech.glide.request.RequestOptions
1013
import com.intive.tmdbandroid.R
14+
import com.intive.tmdbandroid.databinding.HeaderResultsSearchBinding
1115
import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding
1216
import com.intive.tmdbandroid.model.TVShow
1317
import timber.log.Timber
1418
import java.lang.Exception
1519
import java.time.LocalDate
1620
import java.time.format.DateTimeFormatter
1721

18-
class TVShowSearchAdapter() : PagingDataAdapter<TVShow, TVShowSearchAdapter.SearchResultHolder>(REPO_COMPARATOR) {
22+
class TVShowSearchAdapter(private val clickListener: ((TVShow) -> Unit)) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
23+
var query: String = ""
24+
25+
val adapterCallback = AdapterListUpdateCallback(this)
26+
1927
companion object {
20-
private val REPO_COMPARATOR = object : DiffUtil.ItemCallback<TVShow>() {
21-
override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem)
22-
override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem)
28+
private const val HEADER = 0
29+
private const val ITEM = 1
2330
}
24-
}
25-
var query: String = ""
2631

27-
var clickListener: ((TVShow) -> Unit)? = null
32+
val differ = AsyncPagingDataDiffer(
33+
TVShowAsyncPagingDataDiffCallback(),
34+
object : ListUpdateCallback {
35+
override fun onInserted(position: Int, count: Int) {
36+
adapterCallback.onInserted(position + 1, count)
37+
}
38+
39+
override fun onRemoved(position: Int, count: Int) {
40+
adapterCallback.onRemoved(position + 1, count)
41+
}
42+
43+
override fun onMoved(fromPosition: Int, toPosition: Int) {
44+
adapterCallback.onMoved(fromPosition + 1, toPosition + 1)
45+
}
46+
47+
override fun onChanged(position: Int, count: Int, payload: Any?) {
48+
adapterCallback.onChanged(position + 1, count, payload)
49+
}
50+
51+
}
52+
)
53+
54+
suspend fun submitData(tvShowPagingData: PagingData<TVShow>) {
55+
differ.submitData(tvShowPagingData)
56+
}
2857

29-
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TVShowSearchAdapter.SearchResultHolder {
30-
val resultHolder = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
31-
return SearchResultHolder(resultHolder)
58+
override fun getItemCount(): Int {
59+
return differ.itemCount + 1
60+
}
3261

62+
override fun getItemViewType(position: Int): Int {
63+
return when (position) {
64+
0 -> HEADER
65+
else -> ITEM
66+
}
3367
}
3468

35-
override fun onBindViewHolder(holder: TVShowSearchAdapter.SearchResultHolder, position: Int) {
36-
val tvShowItem = getItem(position) as TVShow
37-
holder.bind(tvShowItem)
69+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
70+
return when (viewType) {
71+
HEADER -> HeaderHolder(HeaderResultsSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false))
72+
ITEM -> SearchResultHolder(ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false), clickListener)
73+
else -> throw Exception("Illegal ViewType")
74+
}
3875
}
3976

40-
inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root){
77+
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
78+
when (holder) {
79+
is HeaderHolder -> holder.bind(query)
80+
is SearchResultHolder -> differ.getItem(position - 1)?.let { holder.bind(it) }
81+
}
82+
}
83+
84+
inner class HeaderHolder (private val binding: HeaderResultsSearchBinding): RecyclerView.ViewHolder(binding.root){
85+
fun bind(query: String){
86+
binding.searchHeader.text = binding.root.context.getString(R.string.search_result_header, query)
87+
}
88+
}
89+
90+
inner class SearchResultHolder (private val binding: ItemFoundSearchBinding, private val clickListener: ((TVShow) -> Unit)) : RecyclerView.ViewHolder(binding.root){
4191

4292
private val itemTitle = binding.itemTitleSearch
4393
private val itemYear = binding.itemYearSearch
@@ -47,7 +97,7 @@ class TVShowSearchAdapter() : PagingDataAdapter<TVShow, TVShowSearchAdapter.Sear
4797
fun bind(item: TVShow){
4898

4999
itemView.setOnClickListener {
50-
clickListener?.invoke(item)
100+
clickListener.invoke(item)
51101
}
52102

53103
try {
@@ -87,4 +137,14 @@ class TVShowSearchAdapter() : PagingDataAdapter<TVShow, TVShowSearchAdapter.Sear
87137
}
88138
}
89139

140+
private class TVShowAsyncPagingDataDiffCallback : DiffUtil.ItemCallback<TVShow>() {
141+
override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean {
142+
return oldItem.id == newItem.id
143+
}
144+
145+
override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean {
146+
return oldItem == newItem
147+
}
148+
}
149+
90150
}

app/src/main/res/layout/fragment_search.xml

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -33,38 +33,6 @@
3333

3434
</androidx.appcompat.widget.Toolbar>
3535

36-
<LinearLayout
37-
android:id="@+id/search_header_container"
38-
android:layout_width="match_parent"
39-
android:layout_height="wrap_content"
40-
app:layout_constraintEnd_toEndOf="parent"
41-
app:layout_constraintStart_toStartOf="parent"
42-
app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar"
43-
android:orientation="vertical"
44-
android:visibility="gone">
45-
46-
<TextView
47-
android:id="@+id/search_header_text"
48-
android:layout_width="match_parent"
49-
android:layout_height="wrap_content"
50-
android:paddingStart="16dp"
51-
android:paddingTop="12dp"
52-
android:paddingEnd="16dp"
53-
android:paddingBottom="12dp"
54-
android:textSize="16sp"
55-
app:layout_constraintEnd_toEndOf="parent"
56-
app:layout_constraintStart_toStartOf="parent"
57-
app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" />
58-
59-
<View
60-
android:layout_width="match_parent"
61-
android:layout_height="1dp"
62-
android:background="@color/lightgrey"
63-
app:layout_constraintEnd_toEndOf="parent"
64-
app:layout_constraintStart_toStartOf="parent"
65-
app:layout_constraintTop_toBottomOf="@+id/header" />
66-
</LinearLayout>
67-
6836
<androidx.recyclerview.widget.RecyclerView
6937
android:id="@+id/search_results"
7038
android:layout_width="match_parent"
@@ -74,7 +42,7 @@
7442
app:layout_constraintBottom_toBottomOf="parent"
7543
app:layout_constraintEnd_toEndOf="parent"
7644
app:layout_constraintStart_toStartOf="parent"
77-
app:layout_constraintTop_toBottomOf="@id/search_header_container"
45+
app:layout_constraintTop_toBottomOf="@id/fragment_search_toolbar"
7846
tools:listitem="@layout/item_found_search" />
7947

8048

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="wrap_content">
6+
7+
<TextView
8+
android:id="@+id/search_header"
9+
android:layout_width="match_parent"
10+
android:layout_height="wrap_content"
11+
android:padding="16dp"
12+
app:layout_constraintEnd_toEndOf="parent"
13+
app:layout_constraintStart_toStartOf="parent"
14+
app:layout_constraintTop_toTopOf="parent" />
15+
16+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/layout/layout_search_hint.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
xmlns:app="http://schemas.android.com/apk/res-auto"
55
android:id="@+id/hint_container"
66
android:layout_width="match_parent"
7-
android:layout_height="match_parent">
7+
android:layout_height="match_parent"
8+
android:background="@color/white">
89

910
<ImageView
1011
android:id="@+id/ic_warning_empty_imageView"

0 commit comments

Comments
 (0)