DEV Community

Cover image for Creating custom array adapters in Android, A better way.
Ibanga Enoobong Ime
Ibanga Enoobong Ime

Posted on

4 2

Creating custom array adapters in Android, A better way.

I worked on a project recently and I had tons of spinners to be created, with data populated from a network resource. I first of all began with extending the ArrayAdapter class for each type I had

class Hotel(val name: String, val address: String, val hasPool: Boolean, val hasWifi: Boolean){
override fun toString(): String {
return "Hotel(name='$name', address='$address', hasPool=$hasPool, hasWifi=$hasWifi)"
}
}
class HotelAdapter(context: Context, @LayoutRes private val layoutResource: Int, private val hotels: List<Hotel>):
ArrayAdapter<Hotel>(context, layoutResource, hotels) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
return createViewFromResource(position, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
return createViewFromResource(position, convertView, parent)
}
private fun createViewFromResource(position: Int, convertView: View?, parent: ViewGroup?): View{
val view: TextView = convertView as TextView? ?: LayoutInflater.from(context).inflate(layoutResource, parent, false) as TextView
view.text = hotels[position].name
return view
}
}
view raw WorkTire.kt hosted with ❤ by GitHub

After writing basically the same classes for 2 times, I figured that it didn’t make sense and there must be a better way to do it, after some googling, I discovered I could do without extending ArrayAdapter only problem was that I’d have to override the toString method of the classes with the desired display name, since the default array adapter implementation uses that 😞.

While the awesome Kingsley Adio was reviewing my code, he was quick to point out that I shouldn’t use the toString implementation for UI elements. This lead me to investigate why, I discovered a good answer and that reignited a reason for me to start reading Joshua Bloch’s Effective Java. I’ve had the book for years and heard good things about it but never really got to read it 😁(I’m in Item 27 now! 💪 I also hear the 3rd edition is ready, sigh 😩). The general contract for toString says that the returned string should be “a concise but informative representation that is easy for a person to read”

When practical, the toString method should return all of the interesting information contained in the object — Effective Java

Returning just the desired name doesn’t follow this advice, also as the toString method is very useful in debugging it’s wrong to just twist it to suit your selfish needs.
Another thing to consider is accidental success. Say you forgot to override toString appropriately, you’ll end up seeing stuff like co.package.name.Model@12f114 on the UI, or the bogus toString that comes by default if you use data classes
After some back and forth with him 😍, he suggested that I create an interface with a display name variable which classes that implement will override to set the UI name, then use my custom array adapter repeatedly

interface ModelDisplayName {
val displayName: String
}
class CustomArrayAdapter(context: Context,
@LayoutRes private val layoutResource: Int,
@IdRes private val textViewResourceId: Int = 0,
private val values: List<ModelDisplayName>) : ArrayAdapter<ModelDisplayName>(context, layoutResource, values) {
override fun getItem(position: Int): ModelDisplayName = values[position]
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = createViewFromResource(convertView, parent, layoutResource)
return bindData(getItem(position), view)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = createViewFromResource(convertView, parent, android.R.layout.simple_spinner_dropdown_item)
return bindData(getItem(position), view)
}
private fun createViewFromResource(convertView: View?, parent: ViewGroup, layoutResource: Int): TextView {
val context = parent.context
val view = convertView ?: LayoutInflater.from(context).inflate(layoutResource, parent, false)
return try {
if (textViewResourceId == 0) view as TextView
else {
view.findViewById(textViewResourceId) ?:
throw RuntimeException("Failed to find view with ID " +
"${context.resources.getResourceName(textViewResourceId)} in item layout")
}
} catch (ex: ClassCastException){
Log.e("CustomArrayAdapter", "You must supply a resource ID for a TextView")
throw IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", ex)
}
}
private fun bindData(value: ModelDisplayName, view: TextView): TextView {
view.text = value.displayName
return view
}
}

Things look a whole lot cleaner now!
Learnt something new from this post? Share! and star the repo.

You’ve been in a similar situation? Let me know how you went about it, send me a DM on Twitter!

Reference:
https://www.javaworld.com/article/2073619/core-java/java-tostring---considerations.html

Originally appeared here

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Billboard image

📊 A side-by-side product comparison between Sentry and Crashlytics

A free guide pointing out the differences between Sentry and Crashlytics, that’s it. See which is best for your mobile crash reporting needs.

See Comparison