Skip to content

Instantly share code, notes, and snippets.

@iamnaran
Created February 23, 2021 10:01
Show Gist options
  • Save iamnaran/5702e390098ad57146b6a944bc069b7b to your computer and use it in GitHub Desktop.
Save iamnaran/5702e390098ad57146b6a944bc069b7b to your computer and use it in GitHub Desktop.
Search Recycler View
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
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="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:fitsSystemWindows="true">
<include
android:id="@+id/title_layout"
layout="@layout/item_title_profile"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/search_layout"
layout="@layout/item_search_photos"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_layout" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingTop="10dp"
android:paddingBottom="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
object ApiEndPoints {
const val LOGIN = "login"
const val REGISTER = "register"
const val HOME = "home"
const val PHOTOS_URL = "list"
}
interface ApiHelper {
suspend fun getPhotos(): Response<List<Photos>>
}
interface ApiService {
@POST(ApiEndPoints.LOGIN)
@FormUrlEncoded
suspend fun userLogin(
@Field("email") userEmail: String,
@Field("password") userPassword: String
): Response<User>
@GET(ApiEndPoints.HOME)
suspend fun homeFeeds(): Response<Home>
@GET(ApiEndPoints.PHOTOS_URL)
suspend fun getPhotosUrl(): Response<List<Photos>>
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorContainer" />
<corners android:radius="8dp" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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_margin="15dp"
app:cardCornerRadius="8dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:padding="20dp"
android:text="Hello World"
android:textColor="@color/colorPrimaryText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<SearchView
android:id="@+id/search_view"
android:layout_width="0dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:padding="2dp"
style="@style/SearchViewStyle"
android:textColor="@color/colorPrimaryText"
android:textSize="16sp"
app:iconifiedByDefault="false"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:queryHint="Search Photos" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<TextView
android:id="@+id/toolbar_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:padding="10dp"
android:includeFontPadding="false"
android:text="Photos"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
import com.google.gson.annotations.SerializedName
data class Photos(
@SerializedName("author")
var author: String, // Matthew Wiebe
@SerializedName("download_url")
var downloadUrl: String, // https://picsum.photos/id/1025/4951/3301
@SerializedName("height")
var height: Int, // 3301
@SerializedName("id")
var id: String, // 1025
@SerializedName("url")
var url: String, // https://unsplash.com/photos/U5rMrSI7Pn4
@SerializedName("width")
var width: Int // 4951
)
package com.template.androidtemplate.ui.photos.view
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.SearchView
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.Gson
import com.template.androidtemplate.R
import com.template.androidtemplate.data.model.Photos
import com.template.androidtemplate.ui.photos.adapter.PhotosAdapter
import com.template.androidtemplate.ui.photos.viewmodel.PhotosViewModel
import com.template.androidtemplate.utils.Status
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_photos.*
@AndroidEntryPoint
class PhotosActivity : AppCompatActivity() , SearchView.OnQueryTextListener{
private val photosViewModel: PhotosViewModel by viewModels()
private lateinit var photosAdapter: PhotosAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photos)
setUpViews()
doObserveWork()
}
private fun setUpViews() {
search_layout.findViewById<SearchView>(R.id.search_view)
.setOnQueryTextListener(this)
recycler_view.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.VERTICAL,false)
photosAdapter = PhotosAdapter()
recycler_view.adapter = photosAdapter
}
private fun doObserveWork() {
photosViewModel.progressBarVisibility.observe(this, Observer {
})
photosViewModel.getPhotosFeed().observe(this, Observer {
when (it.status) {
Status.SUCCESS -> {
val gson: Gson = Gson()
renderPhotosList(it.data!!)
}
Status.ERROR -> {
}
Status.LOADING -> {
}
}
})
}
private fun renderPhotosList(photosList: List<Photos>) {
photosAdapter.addData(photosList)
photosAdapter.notifyDataSetChanged()
}
override fun onQueryTextSubmit(query: String?): Boolean {
photosAdapter.filter.filter(query)
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
photosAdapter.filter.filter(newText)
return false
}
}
package com.template.androidtemplate.ui.photos.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.template.androidtemplate.R
import com.template.androidtemplate.data.model.Photos
import kotlinx.android.synthetic.main.item_row_photos.view.*
import kotlin.collections.ArrayList
open class PhotosAdapter :
RecyclerView.Adapter<PhotosAdapter.DataViewHolder>(), Filterable {
//Image world pixels
var photosList: ArrayList<Photos> = ArrayList()
var photosListFiltered: ArrayList<Photos> = ArrayList()
var onItemClick: ((Photos) -> Unit)? = null
inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
onItemClick?.invoke(photosListFiltered[adapterPosition])
}
}
fun bind(result: Photos) {
itemView.name.text = result.id +" "+ result.author
Glide.with(itemView.imageView.context).load(result.downloadUrl).into(itemView.imageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = DataViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_row_photos, parent,
false
)
)
override fun onBindViewHolder(holder: DataViewHolder, position: Int) {
holder.bind(photosListFiltered[position])
}
override fun getItemCount(): Int = photosListFiltered.size
fun addData(list: List<Photos>) {
photosList = list as ArrayList<Photos>
photosListFiltered = photosList
notifyDataSetChanged()
}
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val charString = constraint?.toString() ?: ""
if (charString.isEmpty()) photosListFiltered = photosList else {
val filteredList = ArrayList<Photos>()
photosList
.filter {
(it.id.contains(constraint!!)) or
(it.author.contains(constraint))
}
.forEach { filteredList.add(it) }
photosListFiltered = filteredList
Log.e("performFiltering: t1: ", filteredList.size.toString())
}
return FilterResults().apply { values = photosListFiltered }
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
photosListFiltered = if (results?.values == null)
ArrayList()
else
results.values as ArrayList<Photos>
notifyDataSetChanged()
Log.e("performFiltering: t2 ", "called" + photosListFiltered.size)
}
}
}
}
package com.template.androidtemplate.data.repository
import com.template.androidtemplate.data.api.ApiHelper
import javax.inject.Inject
class PhotosRepository @Inject constructor(
private val apiHelper: ApiHelper
) {
suspend fun photosFeed() = apiHelper.getPhotos()
}
package com.template.androidtemplate.ui.photos.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.template.androidtemplate.data.helper.PreferencesHelper
import com.template.androidtemplate.data.model.Photos
import com.template.androidtemplate.data.repository.PhotosRepository
import com.template.androidtemplate.utils.NetworkHelper
import com.template.androidtemplate.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class PhotosViewModel @Inject constructor(
private val networkHelper: NetworkHelper,
private val preferencesHelper: PreferencesHelper,
private val photosRepository: PhotosRepository
) : ViewModel() {
private val TAG = PhotosViewModel::class.qualifiedName
var progressBarVisibility: MutableLiveData<Boolean> = MutableLiveData()
var onResponse: MutableLiveData<Resource<List<Photos>>> = MutableLiveData()
fun isProgressBarVisible(): LiveData<Boolean> {
return progressBarVisibility
}
fun getPhotosFeed(): LiveData<Resource<List<Photos>>>{
return onResponse
}
init {
doPhotosWork();
}
private fun doPhotosWork() {
viewModelScope.launch {
if (networkHelper.isNetworkConnected()){
photosRepository.photosFeed().let {
if (it.isSuccessful){
onResponse.postValue(Resource.success(it.body()))
// preferencesHelper.setHomeFeeds(it.body())
progressBarVisibility.postValue(false)
}else{
onResponse.postValue(Resource.error(it.errorBody().toString(),null))
progressBarVisibility.postValue(false)
}
}
}
}
}
}
<style name="SearchViewStyle" parent="Widget.AppCompat.SearchView">
<!-- Gets rid of the search icon -->
<!-- <item name="searchIcon">@null</item>-->
<!-- Gets rid of the "underline" in the text -->
<item name="queryBackground">@color/colorContainer</item>
<item name="background">@color/colorContainer</item>
<!-- Gets rid of the search icon when the SearchView is expanded -->
<item name="searchHintIcon">@null</item>
<!-- The hint text that appears when the user has not typed anything -->
<item name="queryHint">@string/search_photos</item>
<item name="android:queryHint">@string/search_photos</item>
<item name="android:background">@drawable/background_search</item>
<!-- <item name="closeIcon">@drawable/ic_clo</item>-->
<item name="android:textColor">@color/colorAccent</item>
<item name="android:textColorSecondary">@color/colorAccent</item>
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment