How to create a reusable RecyclerView using "Composition over Inheritance"? - java

One of my coworkers created an inherit from a RecyclerView and added the logic to create its adapter, defined custom list item attributes, and layout manager inside it.
This is an example of his idea:
class CustomRecyclerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
init {
loadCustomAttributes()
setDefaultAdapter()
setDefaultLayoutManager()
}
private fun loadCustomAttributes() {
// Load custom attributes: item background, for example.
}
private fun setDefaultAdapter() {
// Define specific Custom Adapter.
}
private fun setDefaultLayoutManager() {
// Define specific Layout Manager.
}
fun setData(data: List) {
// Set data and notify dataset changed.
}
private class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
// Specific Adapter.
}
private class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
// Specific View Holder.
}
}
I understand his reasons, and it's very reasonable: he wants to plug a View on the XML and just reuse it, without defining the adapter or anything else on the Activity or Fragment; he will just set the data with the method "setData".
But on the other way, I feel that this class is inheriting a RecyclerView just to breaking the design of the RecyclerView and giving too many responsibilities to it. My pain points are:
A RecyclerView shouldn't decide an item attribute (item background colour, for example);
A RecyclerView shouldn’t know about his LayoutManager;
A RecyclerView shouldn't have methods to update the data on the adapter;
A RecyclerView shouldn’t create an instance of his Adapter.
This is an "inheritance over composition" approach.
I tried to find information about it on the internet, but I didn't find anything about good practices when inheriting from a ListView/RecyclerView.
Another idea would be to wrap it inside another layout (ViewGroup or FrameLayout) to encapsulate it. But this would create an unnecessary nested layout, and it will be complicated to test using Espresso (because now the list is private and shouldn't be exposed).
My question is: how to create a reusable RecyclerView using "Composition over Inheritance"?

Related

How to make a multiple recyclerViews which contains posts recyclerview and stories recyclerView similar to facebook app?

I'm trying to make something like below:
On that screenshot of facebook lite app:
At part marked "1": is a vertical recyclerview which contains posts.
At part marked "2": is a horizontal recyclerview which contains the stories.
At part marked "3": is the same recyclerview as at part marked "1" which contains posts.
I have already made the recyclerview for posts and It works well. Now I want to know how should I make the recyclerview for stories or friendship suggestion and make the two recyclerviews appear like on Facebook app ?
How could I have recyclerviews similar to the one in facebook app ?
Facebook show multiples recyclerViews One vertical where it shows the posts some others horizontal where it
shows stories or sometimes friendship suggestion.
Do you undestand me ?
Please tell me if I should explain more my issue.
Thanks.
you can define different View Holders for a Recycler List
the horizontal lists are just a view holder with another recycler inside them but with horizontal orientation in the main list. you can choose which view holder you want to use in OnCreatViewHolder
You should implement RecyclerView with multi-view type which gives you the various layout on a single RecyclerView adapter.
By doing this you need a model class which should have a field for define the different type.
Please have a look below sample code
Sample Model class
data class Sample(
var a: String = ""
...
var type: String = ""
)
Field type in model class above will define a unique view-type of adapter
Sample Adapter Class
class SampleAdapter(
private val context: Context,
private val items: ArrayList<Sample>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val HEADER = 1
private const val STORY = 2
private const val FEED = 3
}
override fun getItemViewType(position: Int): Int {
return when(items[position].type) {
Constant.HEADER -> HEADER
Constant.STORY -> STORY
else -> FEED
}
}
override fun getItemCount(): Int = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
HEADER -> HeaderViewHolder(LayoutInflater.from(context).inflate(R.layout_header, parent, false))
STORY -> StoryViewHolder(LayoutInflater.from(context).inflate(R.layout_story, parent, false))
else -> FeedViewHolder(LayoutInflater.from(context).inflate(R.layout_feed, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is HeaderViewHolder -> holder.onBind()
is StoryViewHolder -> holder.onBind()
is FeedViewHolder -> holder.onBind()
}
}
inner class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
inner class StoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
inner class FeedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
}
And when you are using the above adapter in Activity or Fragment. You should define the type of each item in ArrayList
For example: samples.add(Sample(...., type=Constant.Header))

Should Android listView adapter have api calls

Hi I have a adapter which displays the list of items and all the functions related to setItems, getCount, notifyDataSetChanged.
adapter also has calls to api's through use cases.
Structure is
Adapter -> UseCase -> Repository -> apiLayer
I am aware that fragemnts and activities should not contains calls to api (usecases in my instance).
So should adapter's have api calls (usecases in my instance)
Thanks
R
It is possible, but from a software design point of view I would not recommend it.
The Adapter's responsibility is to connect your Data with the View and make it available to the ListView/RecyclerView. The Adapter should not have any other dependency (knowledge). This will also make it more robust to changes.
So you should consider that only the Activity/Fragment talks to your Presenter and delegates the results from the Presenter to the Adapter.
This will also make (unit) testing (of the Presenter) more easier.
class YourActivity: Activity() {
private val presenter: YourPresenter = // ...
override fun onCreate() {
val adapter: YourAdapter = YourAdapter()
recyclerView.setAdapter(adapter)
val data = presenter.getData()
adapter.submit(data)
}
}
class YourPresenter(private val useCase: UseCase : Presenter() {
fun getData(): List<Data> {
return useCase.fetchData()
}
}

Android. Recycler view with mutiple view types

I have recyclerView with 2 view types (item and FOOTER). I need to show my footer on the bottom of screen even if no items or items size is 1. Is it possible to implement it? Now my footer is showing after last item, but I need show footer always on the bottom.
You need a data structure that allows you to do so, and then you need the view holders for supporting it, once that is done handling the conditional flows on the adapter and should be good to go.
Usually, in Kotlin we use a sealed class which allows very good type control
sealed class AdapterRow {
data class RegularItem(/*your values*/) : AdapterRow()
data class FooterItem(/*your values*/) : AdapterRow()
//if the footer is always the same maybe object FooterItem : AdapterRow() is more suitable
}
It's a nice "trick" to have the sealed descendants inside so that way the sealed parent makes a domain space name, then you call them like this AdapterRow.RegularItem(...). If you don't like that, the sealed class has one constraint, descendants must be on the same file.
Then you need a view holder for supporting each type (view holder and the view in the layout if needed). In this case we are gonna use an abstract class to take advantage of the polymorphism and abstract methods
//you could use binding here and then the implementation define the binding type
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(row: AdapterRow)
}
And then the children:
class RegularItemViewHolder(view: View) : BaseViewHolder(view) {
override fun bind(row: AdapterRow) {
//validate the data you are receiving is correct
if (row !is AdapterRow.RegularItem) return
//do your data bindings, view findings or whatever
}
}
With the above you can deduce the other view holder, now the adapter methods
getItemViewType(position: Int) {
when(getItem(position)) {
is AdapterRow.RegularItem -> R.layout.REGULAR_ITEM_LAYOUT
is AdapterRow. FooterItem -> R.layout.FOOTER_ITEM_LAYOUT
}
}
onCreateViewHolder(...) {
return when (viewType) {
R.layout.REGULAR_ITEM_LAYOUT -> {
RegularItemViewHolder(...)
}
R.layout.FOOTER_ITEM_LAYOUT {
//same but for footer
}
else -> throw RuntimeException("Unsupported view Holder)
}
}
onBindViewHolder(...) {
holder.bind(getItem(position))
}
The data construction part is the last thing, in somewhere else, your fragment, the view model, etc, you have to create the data structure as you need. By example:
fun makeRows(texts: List<String>): List<AdapterRow> {
val rows = mutableListOf<AdapterRow>()
texts.forEach { text ->
//do some mapping from what ever is your source in this case strings
AdapterRow.RegularItem(...text)
}
//so it doesn't matter if the source of data is empty at the end you always add the footer
rows.add(AdapterRow.FooterItem)
}
And then is just passing the data to the adapter, if you are using ListAdapter
val rows = someClass.makeRows(....)
yourAdapter.submitList(rows)

Android Recyclerview - How to have a different column count per row, based on child size?

I need to develop a tag picker, like the one Foursquare uses for tastes, and the one Flipboard uses for "finding new topics."
I came across this library, Foursquare-CollectionPicker at github.
However, it uses a linear layout, which can reduce the performance for numerous child views when scrolling.
Hence, I need to use a recyclerview. Can anyone suggest how to replicate this with a recyclerview? My problem is that for each row in the recyclerview, the column count could be different, based on the size/number of the child views in each row (tags, in this case).
Thank you.
You could use FlexboxLayoutManager in a recycler view. All you need to do is create the layout manager as below. Don't forget to add your own recyclerAdapter to the recycler view as well of course.
FlexboxLayoutManager layoutManager = new FlexboxLayoutManager(context);
layoutManager.setFlexWrap(FlexWrap.WRAP);
recyclerView.setLayoutManager(layoutManager);
https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
https://blog.devcenter.co/unboxing-the-flexboxlayout-a7cfd125f023
If all you need is a recycler view that changes the number of columns (using a standard Google provided GridLayoutManager in RecyclerView), you don't need any custom code at all.
(pseudo code)
Prerequisites
You use a RecylerView with a GridLayoutManager (import androidx.recyclerview.widget.GridLayoutManager)
Your Adapter has a Type (so different viewTypes can inflate different ViewHolders).
You can initialize your grid Layout like:
private lateinit var layoutManager: GridLayoutManager
private val adapter = YourAdapter()
Activity#onCreate(...) {
super.onCreate(savedInstance)
setContentView(...)
layoutManager = GridLayoutManager(this, 2) //defaults to two columns
yourRecyclerView.layoutmanager = layoutManager //set it
yourRecyclerView.adapter = adapter
//Here goes the magic
}
What is the Magic?
Something like this:
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter.getItemViewType(position)) {
adapter.viewTypeOneColumn -> 1
adapter.viewTypeTwoColumns -> 2
else -> -1
}
}
}
This obviously assumes you have a "view type" (of any sort)
it can be as simple as:
class YourAdapter : ... {
internal val viewTypeOneColumn = 0
internal val viewTypeTwoColumns = 1
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
viewTypeOneColumn -> ViewHolderForOneColumn(...)
viewTypeTwoColumns -> ViewHolderForTwoColumns(...)
else -> throw IllegalArgumentException("You must supply a valid type for this adapter")
}
}
override fun getItemViewType(position: Int): Int {
return getItem(position).someThingThatClassifiesThem // this obviously depend on what you use to define what each item is...
}
}
And that's all you really need.
I have once created a sample that does this for displaying "ads" inside a RecyclerView: You can check it here (it was updated a while ago but the code still works the same).
https://github.com/Gryzor/GridToShowAds
You will need to write your own layout manager, take a look at these blogs:
http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/
http://simpleandstupid.com/2015/05/01/recyclerview-and-its-custom-layoutmanager/

How to instantiate multiple custom views in Android?

I'm really confused about custom views.
I need to define a custom view, consists of an ImageView and a TextView. And then I want to change the contents of this views, according to a php json response, which I have accomplished.
First of all, which way should I go :
1) Define the custom view as an XML, then "inflate" , duplicate, whatever, and then change the newly instantiated text's and image sources etc ?
2) Define the custom view as a Java class, and instantiate it ?
In the end, I want to instantiate my custom views as children of a vertical layout.
What I'm currently trying is, path #2. I defined this class :
public class ArizaSatiri extends LinearLayout {
TextView arizaTitle;
//constructor :
public ArizaSatiri(Context context, AttributeSet attrs)
{
super(context, attrs);
// add title , description etc :
arizaTitle = new TextView(context);
arizaTitle.setText("abcef defefef");
this.addView(arizaTitle);
}
}
Then I tried this in my main activity :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_arizalarim);
ArizaSatiri as = new ArizaSatiri(getApplicationContext(), attrSet);
}
But I have no idea how to construct an AttributeSet.
So please tell me, which path should I choose, and how to accomplish to instantiate a custom view, as many times as I want, dynamically ?
Attribute set is constructed when you add your component via xml. You have to define custom component's attributes in attrs.xml. (http://www.vogella.com/articles/AndroidCustomViews/article.html#additional_attributes)
If you do not want to create your view from xml then just remove attribute set from the constructor as LinearLayout has a constructor without the attr set: http://developer.android.com/reference/android/widget/LinearLayout.html#LinearLayout(android.content.Context)

Categories