Current issue is a bit complicated, so I will outline some words with "" so whoever reads this could be able to understand text below.
I have a "page viewer", which implements "recycler view adapter".
Each page has 4 textviews inside it, i pass text to "adapter" through "constructor parameter".
My goal is to get position of textviews (filled with text), and use it to set margin to views in main fragment (which contains page viewer).
I tried to do it with help of "view model live data", so i passed setters for "view model strings live data" as "lambda" to "recycler view adapter" and I tried to "set text" and "get position" for "strings live data" in onBindViewHolder fun. Here is the adapter:
class PagerAdapter(private val spannableStrings: List<List<SpannableStringBuilder>>,
val liveDataSetters: List<(Int)->Unit>) : RecyclerView.Adapter<PagerAdapter.ViewHolder>() {
var location1 = IntArray(2)
var location2 = IntArray(2)
var location3 = IntArray(2)
var location4 = IntArray(2)
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView1: TextView? = null
var textView2: TextView? = null
var textView3: TextView? = null
var textView4: TextView? = null
init {
textView1 = itemView.findViewById(R.id.textview1)
textView2 = itemView.findViewById(R.id.textview2)
textView3 = itemView.findViewById(R.id.textview3)
textView4 = itemView.findViewById(R.id.textview4)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item, parent, false))
override fun getItemCount(): Int = spannableStrings.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (position==0) {
holder.textView1?.text = spannableStrings[0][0]
holder.textView2?.text = spannableStrings[0][1]
holder.textView3?.text = spannableStrings[0][2]
holder.textView4?.text = spannableStrings[0][3]
holder.textView1?.getLocationOnScreen(location1)
liveDataSetters[0](location1[1])
holder.textView2?.getLocationOnScreen(location2)
liveDataSetters[1](location2[1])
holder.textView3?.getLocationOnScreen(location3)
liveDataSetters[2](location3[1])
holder.textView4?.getLocationOnScreen(location4)
liveDataSetters[3](location4[1])
}
else {
holder.textView1?.text = spannableStrings[1][0]
holder.textView2?.text = spannableStrings[1][1]
holder.textView3?.text = ""
holder.textView4?.text = ""
holder.textView1?.getLocationOnScreen(location1)
liveDataSetters[0](location1[0])
holder.textView2?.getLocationOnScreen(location2)
liveDataSetters[1](location2[0])
}
}
}
Here is code snippet regarding this from main fragment:
binding.viewpager2.adapter= activity?.let {
PagerAdapter(listOf(
listOf(text1a, text2a, text3a, text4a), listOf(text1b, text2b)
),
listOf(viewModel.setLocation1, viewModel.setLocation2, viewModel.setLocation3, viewModel.setLocation4)
)
}
TabLayoutMediator(binding.tabLayout, binding.viewpager2) { tab, position ->
}.attach()
Code snippet from view model:
private var _location1 = MutableLiveData<String>()
val location1: LiveData<String> = _location1
val setLocation1: (Int) -> Unit = { value -> location1.value=value.toString()+"dp"}
...
And finally xml:
<layout 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">
<data>
<variable
name="viewModel"
type="com.example.project.ui.main.view.viewModel" />
</data>
...
<ImageView
android:id="#+id/pointer1a"
android:layout_width="42dp"
android:layout_height="26dp"
android:layout_marginTop="#{viewModel.location1}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
...
However, what i recieve on launch is:
error: cannot find symbol
import com.example.project.databinding.FragmentBindingImpl;
As far as i understand, no live data could be passed to xml margin top parameter, because whenever i set this data to, let's say, random textview as text, everythin works fine. How can i solve this case? Maybe there are any alternatives or workarounds that i am missing?
Related
I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.
I have tried it with and without the boolean in the data class. I wasn't sure if that was required.
Here is my code:
Main Activity:`
class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0
private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = secondsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
getDuration()
addSecondsToRecyclerView()
}
}
fun getDuration(): Int {
val endTime = SystemClock.elapsedRealtime()
val elapsedMilliSeconds: Long = endTime - startTime
val elapsedSeconds = elapsedMilliSeconds / 1000.0
displaySeconds = elapsedSeconds.toInt()
return displaySeconds
}
private fun generateSecondsList(): ArrayList<Seconds> {
return ArrayList()
}
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
Adapter:
var adapterSeconds = MainActivity().getDuration()
class Adapter(
private val rvDisplay: MutableList<Seconds>
) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView1: TextView = itemView.tv_seconds
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_item,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
currentDisplay.isRed = adapterSeconds > 5
holder.itemView.apply {
val redColor = ContextCompat.getColor(context, R.color.red).toString()
val blackColor = ContextCompat.getColor(context, R.color.black).toString()
if (currentDisplay.isRed) {
holder.textView1.setTextColor(redColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
} else {
holder.textView1.setTextColor(blackColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
}
}
}
override fun getItemCount() = rvDisplay.size
}
Data Class:
data class Seconds(
var rvSeconds: Int,
var isRed: Boolean
)
when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.
you could do something like
private var secondsList = generateSecondsList() // make this var
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
if ( /* TODO check if time has passed */) {
secondsList = secondsList.map { it.isRed = true }
secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
} else {
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.
Your code isn't really working because of this:
var adapterSeconds = MainActivity().getDuration()
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
...
currentDisplay.isRed = adapterSeconds > 5
...
}
You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:
class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
private var displayRed = false
set(value) {
field = value
// Refresh the display - the ItemChanged methods mean something about the items
// has changed, rather than a structural change in the list
// But you can use notifyDataSetChanged if you want (better to be specific though)
notifyItemRangeChanged(0, itemCount)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
if (displayRed) {
// show things as red - you shouldn't need to store that state in the items
// themselves, it's not about them - it's an overall display state, right?
} else {
// display as not red
}
}
So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?
Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:
// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)
That's it! Using any view, post a delayed function that tells the adapter to display as red.
It might be more helpful to store that runnable as an object:
private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)
because then you can easily cancel it
recyclerView.removeCallbacks(showRedTask)
Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!
I finally figured it out using a companion object in Main Activity with a boolean set to false. If the time exceeded 5 seconds, then it set to true.
The adapter was able to recognize the companion object and change the color of seconds to red if they exceeded 5.
While I can scroll to a specific index of my RecyclerView I cannot scroll to an item that satisfies a given Matcher that relies upon the values of data bound values.
If I hard code the layouts of the items inserted into the RecyclerView with values then the Matcher finds them but if these values are filled through data binding the values the Matcher is comparing are all "empty". This happens even though the pending bindings have all been completed and no amount of waiting (or sleeping) or anything will make them available.
It is also interesting to note that Espresso's "withText" Matcher can 'see' the values when run as a simple assertion but when used as a Matcher in the "scrollTo(...)" method it fails to find them.
It's worth noting that the "scrollTo(...)" method does cause my adapter's "onCreateViewHolder(...)" to be run again but the adapter's "onBindViewHolder(...)" is also run before the checks start and inserting IdlingResource blockers in these methods does not help.
Any idea what is happening here?
Edit: I have made a simple stand alone project that illustrates this issue in an attempt to isolate the problem.
Adding specific code as an example
The "text_row_item"s Layout file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="text"
type="String"
/>
</data>
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{text}"
/>
</layout>
My Adapter looks like this:
class CustomAdapter(private val dataSet: List<String>) :
RecyclerView.Adapter<CustomAdapter.CustomViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.context),
R.layout.text_row_item,
viewGroup,
false)
binding.executePendingBindings()
return CustomViewHolder(binding)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
holder.bind(dataSet[position])
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
}
}
override fun getItemCount() = dataSet.size
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = CustomAdapter(listOf(
"W00t",
"W01t",
.
.
.
"W41t",
"W42t"
))
}
}
And my actual Espresso test file itself contains:
#Test
fun checkW00_42t() {
withText("W00t").assertAny(isDisplayed())
checkText("W00t")
checkText("W01t")
.
.
.
checkText("W41t")
checkText("W42t")
}
private fun checkText(text: String) {
Espresso.onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(text))))
withText(text).assertAny(isDisplayed())
And this is what this app looks like at the time the test is run:
There are 43 "W00t"s added to the RecyclerView so that it will need to be scrolled to see the last ones however it fails to find even the first one that doesn't require scrolling at all.
It's worth noting that the first check (which doesn't use "scrollTo(...)") passes:
withText("W00t").assertAny(isDisplayed())
However attempting to scroll to the exact same text fails:
checkText("W00t")
It is also worth noting that if instead of using DataBinding one were to just assign the text in onBindViewHolder, as in:
viewHolder.textView.text = dataSet[position]
the scrollTo(...) works.
Oh my goodness, I think I have it!
I had to move executePendingBindings() from onCreateViewHolder(...) to CustomViewHolder():
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): CustomViewHolder {
val binding: ViewDataBinding = DataBindingUtil.inflate(...)
// binding.executePendingBindings() // ** remove here **
return CustomViewHolder(binding)
}
inner class CustomViewHolder(private val binding: ViewDataBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.setVariable(BR.text, item)
binding.executePendingBindings() // ** insert here **
}
}
So I am trying to populate two Spinners in the same Fragment, both using the same list, but to display different items.
I have the following data class:
data class ProductTypeObject (
//ProductType fields (2 fields)
var productType: String = "",
var productGroup: String = "",
#ServerTimestamp
var dateEditedTimestamp: Date? = null,
#Exclude #set:Exclude #get:Exclude
var productTypeID: String = ""
) : Serializable {
override fun toString(): String {
return productType
}
}
The Spinner is populated in the Fragment when the list is observed from the ViewModel as below:
// Observe ProductTypes and populate Spinner
businessViewModel.allAppDataProductTypes.observe(viewLifecycleOwner, Observer { productTypeArrayList ->
if (!productTypeArrayList.isNullOrEmpty()){
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, productTypeArrayList)
binding.inventoryAddEditProductGroupSpinner.adapter = adapter
}
})
This shows a list of product types as I have specified this in the toString()of the object, but is there a way to direct a second Spinner to show a list ofproduct group?
If you don't need to retrieve the values from the spinners, it's easiest to map the values to a new list:
businessViewModel.allAppDataProductTypes.observe(viewLifecycleOwner, Observer { productTypeArrayList ->
if (!productTypeArrayList.isNullOrEmpty()){
//...
val adapter2 = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item,
productTypeArrayList.map(ProductTypeObject::productGroup)
//...
}
})
If you need both Spinners to be able to retrieve the original item type, then you can't use the ArrayAdapter class as is, since it relies purely on the toString() of your class. You can subclass it like this for a more flexible version that lets you pass property or lambda that is used instead of toString(). I didn't test it, but I think it will do what you want. If you use this class, you don't need to override toString() in your original data class.
class CustomArrayAdapter<T : Any>(
context: Context,
items: List<T>,
val itemToCharSequence: T.() -> CharSequence = Any::toString
) : ArrayAdapter<T>(context, 0, items) {
private val inflater = LayoutInflater.from(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return (convertView ?: inflater.inflate(android.R.layout.simple_spinner_item, parent, false))
.apply {
val item = getItem(position)!! // will never be null inside getView()
(this as TextView).text = itemToCharSequence(item)
}
}
}
Usage:
val typeAdapter = CustomArrayAdapter(requireContext(), productTypeArrayList, ProductTypeObject::productType)
val groupAdapter = CustomArrayAdapter(requireContext(), productTypeArrayList, ProductTypeObject::productGroup)
I am trying to implement deep linking with branch and having issues with the Share Sheet. It just doesn't work. No matter what I click, the relevant action doesn't happen and it just goes back to the bottom of the screen. Even when I click copy, the text doesn't copy. there are no errors so I don't know what's wrong.
This is my code (it is a single item in a recyclerView. I am using GroupieAdapter):
class SingleCommunityOption(val community: Community, val activity : MainActivity) : Item<ViewHolder>() {
private lateinit var buo: BranchUniversalObject
private lateinit var lp: LinkProperties
override fun getLayout(): Int {
return R.layout.community_option_layout
}
override fun bind(viewHolder: ViewHolder, position: Int) {
val firebaseAnalytics = FirebaseAnalytics.getInstance(viewHolder.root.context!!)
val title = viewHolder.itemView.community_option_title
val description = viewHolder.itemView.community_option_description
val memberCount = viewHolder.itemView.community_option_members_count
val share= viewHolder.itemView.community_share
title.text = community.title
description.text = community.description
memberCount.text = "${community.members}"
buo = BranchUniversalObject()
.setCanonicalIdentifier(community.id)
.setTitle(community.title)
.setContentDescription("")
.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC)
.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC)
.setContentMetadata(ContentMetadata().addCustomMetadata("type", "community"))
lp = LinkProperties()
buo.listOnGoogleSearch(viewHolder.root.context)
share.setOnClickListener {
val ss = ShareSheetStyle(activity, "Republic invite", "Join me in this republic.")
.setCopyUrlStyle(activity.resources.getDrawable(android.R.drawable.ic_menu_send), "Copy", "Added to clipboard")
.setMoreOptionStyle(activity.resources.getDrawable(android.R.drawable.ic_menu_search), "Show more")
.addPreferredSharingOption(SharingHelper.SHARE_WITH.FACEBOOK)
.addPreferredSharingOption(SharingHelper.SHARE_WITH.FACEBOOK_MESSENGER)
.addPreferredSharingOption(SharingHelper.SHARE_WITH.WHATS_APP)
.addPreferredSharingOption(SharingHelper.SHARE_WITH.TWITTER)
.setAsFullWidthStyle(true)
.setSharingTitle("Share With")
buo.showShareSheet(activity, lp, ss, object : Branch.BranchLinkShareListener {
override fun onShareLinkDialogLaunched() {}
override fun onShareLinkDialogDismissed() {}
override fun onLinkShareResponse(sharedLink: String, sharedChannel: String, error: BranchError) {}
override fun onChannelSelected(channelName: String) {
firebaseAnalytics.logEvent("community_shared_$channelName", null)
}
})
}
}
}
I have a recyclerView. When I do the pull to refresh, if the new data is just one list item, then the recycler view loads the item perfectly. But if the updated data contains 2 or more, then I think the view is not recycled properly. In the actionContainer, there should only one item to be added for each of the updated list item. But during pull to refresh, ONLY WHEN there are 2 or more list items to be updated, the actionContainer shows 2 data where it should be only one. Can someone help me to fix this?
override fun onBindViewHolder(holder: HistoryListAdapter.ViewHolder?, position: Int) {
info("onBindViewHolder =>"+listAssets.size)
info("onBindViewHolder itemCount =>"+itemCount)
info("onBindViewHolder position =>"+position)
val notesButton = holder?.notesButton
val notesView = holder?.notesTextView
val dateTime = listAssets[position].date
val location = listAssets[position].location
val sessionId = listAssets[position].id
holder?.sessionID = sessionId
holder?.portraitImageView?.setImageDrawable(listAssets[position].image)
holder?.titleTextView?.text = DateTimeFormatter.getFormattedDate(context, dateTime)
val timeString = DateTimeFormatter.getFormattedTime(context, dateTime)
if (location.length != 0) {
holder?.subtitleTextView?.text = "$timeString # $location"
} else {
holder?.subtitleTextView?.text = "$timeString"
}
val data = listAssets[position].data
for (actionData in data) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val parent = inflater.inflate(R.layout.history_card_action, null)
val icon = parent?.findViewById(R.id.historyActionIcon) as ImageView
val title = parent?.findViewById(R.id.historyActionTitle) as TextView
val subtitle = parent?.findViewById(R.id.historyActionSubtitle) as TextView
var iconDrawable: Drawable? = null
when(actionData.type) {
ActionType.HEART -> {
iconDrawable = ContextCompat.getDrawable(context, R.drawable.heart)
}
ActionType.LUNGS -> {
iconDrawable = ContextCompat.getDrawable(context, R.drawable.lungs)
}
ActionType.TEMPERATURE -> {
iconDrawable = ContextCompat.getDrawable(context, R.drawable.temperature)
}
}
icon.setImageDrawable(iconDrawable)
val titleString = actionData.title
titleString?.let {
title.text = titleString
}
val subtitleString = actionData.subtitle
subtitleString?.let {
subtitle.text = subtitleString
}
holder?.actionContainer?.addView(parent)
}
val notes = listAssets[position].notes
notesView?.text = notes
if (notes.length == 0) {
notesButton?.layoutParams?.width = 0
} else {
notesButton?.layoutParams?.width = toggleButtonWidth
}
if (expandedNotes.contains(sessionId)) {
notesView?.expandWithoutAnimation()
} else {
notesView?.collapseWithoutAnimation()
}
notesButton?.onClick {
notesView?.toggleExpansion()
}
}
data class ListAssets(val id: String,
val date: Date,
val location: String,
val notes: String,
val image: Drawable,
val data: ArrayList<ListData>)
data class ListData(val type: ActionType,
val title: String?,
val subtitle: String?)
override fun onViewRecycled(holder: HistoryListAdapter.ViewHolder?) {
super.onViewRecycled(holder)
if (holder != null) {
holder.actionContainer.removeAllViewsInLayout()
holder.actionContainer.removeAllViews()
val notesTextView = holder.notesTextView
if (notesTextView != null) {
if (notesTextView.expandedState) {
val sessionID = holder.sessionID
sessionID?.let {
val sessionSearch = expandedNotes.firstOrNull {
it.contentEquals(sessionID)
}
if (sessionSearch == null) {
expandedNotes.add(sessionID)
}
}
} else {
val sessionID = holder.sessionID
sessionID?.let {
val sessionSearch = expandedNotes.firstOrNull {
it.contentEquals(sessionID)
}
if (sessionSearch != null) {
expandedNotes.remove(sessionSearch)
}
}
}
}
}
}
First, you should probably not override onViewRecycled() unless you have to perform some very particular resources cleanup.
The place where you want to setup your views before display is onBindViewHolder().
Second, you don't need not add or remove views dynamically in a RecyclerView item, it's simpler and more efficient to only switch the visibility of the view between VISIBLE and GONE. In cases where this is not enough because views are too different, you should declare different view types, which will create separate ViewHolders for each view type.
You should not remove or add any view while overriding onBindViewHoder() method of RecyclerView Adapter because next time when a recycled layout is used, the removed views will not be found. Instead of this you can use show/hide on a view.
If you add any view to the layout dynamically, later on when this layout is recycled, it also contains the extra view which you have added before.
Similarly, if you remove any view from the layout dynamically, later on when this layout is recycled, it does not contain the view which you have removed earlier.
I have implemented a RecyclerView and Retrofit,it has the SwipeView layout (Pull to Refresh).Here is the link to the repisitory.
https://github.com/frankodoom/Retrofit-RecyclerVew