Giter Site home page Giter Site logo

Comments (14)

nitrico avatar nitrico commented on May 17, 2024 1

Hello @dalewking,

by "complex interactions" you mean handling different click listeners for different elements within the same item type? If so, as you said, you can do that using the OnBindListener but, again as you said, there is only one for all the item types so the code wouldn't be so clean. However, there's another way you can achieve that using this library and it is handling the clicks in the layout itself. I think that way will fit your needs. Here's an example:

<layout ...>

    <data>
        <variable name="item" type="...Item" />
    </data>

    <LinearLayout ...>

        <ImageView ...
            android:onClick="@{v -> item.onItemImageClick(v)}" />
        <TextView ...
            android:onClick="@{v -> item.onItemTitleClick(v)}" />
        <TextView ...
            android:onClick="@{v -> item.onItemDescriptionClick(v)}" />

    </LinearLayout>

</layout>
public class Item {

    ...

    public void onItemImageClick(View view) {
        // handle click on image
    }

    public void onItemTitleClick(View view) {
        // handle click on title
    }

    public void onItemDescriptionClick(View view) {
        // handle click on description
    }

}

FYI you should add support for unbinding as well to clean up things when a view is removed from the list

Can you provide more information about that?

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

Please, let me know if that worked for you.

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

Well, yes and mostly no. Sure, of course everything works if you can do everything through calls on the item itself, but sometimes the item is just a dumb model object that doesn't have the ability to handle the extra functionality. Perhaps it is cleaner if the list doesn't contain a bunch of dumb model objects but instead is a list of item view models that wrap the dumb model objects, but it would be nice to not force that on to the user.

In my case I have items that have enable/disable switches on them and the ability to delete them (along with other items that are just headers and have no functionality). The items themselves are just dumb data objects and you have to pass them to other calls to handle those operations.

If there was a way to create your own type specific ViewHolder classes that would be much more powerful.

The thought on unbind was some way to notify the user that onViewRecycled was called on the holder so they can release resources.

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

I will consider adding this kind of functionality for next versions. Actually, I thought about it before but decided not to include it as the intention was to use Data Binding, and listeners (onBind/onClick...) are there for cases where you can't use Data Binding or just want to declare the functionality in a quick and explicit way.

However, I don't think that creating the specific ViewHolders is the way to do it, at least for this library, as the point here is actually avoiding them... More "customizable" listeners for specific item types could be an option, though.

Meanwhile, I want to let you know that you can still keep your dumb models and handle the clicks in other classes:

<layout ...>

    <data>
        <variable name="item" type="...Item" />
        <import type="...Listeners" />
    </data>

    <LinearLayout ...>

        <ImageView ...
            android:onClick="@{v -> Listeners.onItemImageClick(item, v)}" />
        <TextView ...
            android:onClick="@{v -> Listeners.onItemTitleClick(item, v)}" />
        <TextView ...
            android:onClick="@{v -> Listeners.onItemDescriptionClick(item, v)}" />

    </LinearLayout>

</layout>
public class Listeners {

    public static void onItemImageClick(Item item, View view) {
        // handle click on image
    }

    public static void onItemTitleClick(Item item, View view) {
        // handle click on title
    }

    public static void onItemDescriptionClick(Item item, View view) {
        // handle click on description
    }

}

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

I know it is pretty much the same, but now you are keeping your models classes as simple as possible and can handle the events anywhere else, maybe even as an internal class in the Activity/Fragment where you are using the adapter.

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

I tried playing with it a bit and here is what I came up with moving modified OnBindListener to map call and adding OnUnbindListener as a patch file:

diff --git a/lastadapter/src/main/java/com/github/nitrico/lastadapter/LastAdapter.kt b/lastadapter/src/main/java/com/github/nitrico/lastadapter/LastAdapter.kt
index e8827e9..6de0ab5 100644
--- a/lastadapter/src/main/java/com/github/nitrico/lastadapter/LastAdapter.kt
+++ b/lastadapter/src/main/java/com/github/nitrico/lastadapter/LastAdapter.kt
@@ -27,9 +27,8 @@ import android.view.ViewGroup

 class LastAdapter private constructor(private val list: List<Any>,
                                       private val variable: Int,
-                                      private val map: Map<Class<*>, Int>,
+                                      private val map: Map<Class<*>, Mapping<*>>,
                                       private val layoutHandler: LayoutHandler?,
-                                      private val onBindListener: OnBindListener?,
                                       private val onClickListener: OnClickListener?,
                                       private val onLongClickListener: OnLongClickListener?)
 : RecyclerView.Adapter<LastAdapter.ViewHolder>() {
@@ -38,17 +37,32 @@ class LastAdapter private constructor(private val list: List<Any>,
         @JvmStatic fun with(list: List<Any>, variable: Int) = Builder(list, variable)
     }

+    class Mapping<T>(val layout: Int, val bindListener: OnBindListener<T>?,
+                     val unbindListener: OnUnbindListener?) {
+
+        @Suppress("UNCHECKED_CAST")
+        fun onBind(item: Any, view: View, binding: ViewDataBinding, position: Int)
+            = bindListener?.onBind(item as T, view, binding, position)
+
+        fun unBind(binding: ViewDataBinding) {
+            unbindListener?.onUnbind(binding)
+        }
+    }
+
     class Builder internal constructor(private val list: List<Any>, private val variable: Int) {

-        private val map = mutableMapOf<Class<*>, Int>()
+        private val map = mutableMapOf<Class<*>, Mapping<*>>()
         private var handler: LayoutHandler? = null
-        private var onBind: OnBindListener? = null
         private var onClick: OnClickListener? = null
         private var onLongClick: OnLongClickListener? = null

-        fun map(clazz: Class<*>, layout: Int) = apply { map.put(clazz, layout) }
+        fun <T> map(clazz: Class<T>, layout: Int, bindListener: OnBindListener<T>? = null,
+                    unbindListener: OnUnbindListener? = null)
+                = apply { map.put(clazz, Mapping(layout, bindListener, unbindListener)) }

-        inline fun <reified T: Any> map(layout: Int) = map(T::class.java, layout)
+        inline fun <reified T: Any> map(layout: Int, bindListener: OnBindListener<T>? = null,
+                    unbindListener: OnUnbindListener? = null)
+                = map(T::class.java, layout, bindListener, unbindListener)

         fun layoutHandler(layoutHandler: LayoutHandler) = apply { handler = layoutHandler }

@@ -57,13 +71,6 @@ class LastAdapter private constructor(private val list: List<Any>,
                     = ItemPosition(item, position).f()
         })

-        fun onBindListener(listener: OnBindListener) = apply { onBind = listener }
-
-        inline fun onBind(crossinline f: ItemViewTypePosition.() -> Unit) = onBindListener(object: OnBindListener {
-            override fun onBind(item: Any, view: View, type: Int, position: Int)
-                    = ItemViewTypePosition(item, view, type, position).f()
-        })
-
         fun onClickListener(listener: OnClickListener) = apply { onClick = listener }

         inline fun onClick(crossinline f: ItemViewTypePosition.() -> Unit) = onClickListener(object: OnClickListener {
@@ -80,14 +87,16 @@ class LastAdapter private constructor(private val list: List<Any>,

         fun into(recyclerView: RecyclerView) = build().apply { recyclerView.adapter = this }

-        fun build() = LastAdapter(list, variable, map, handler, onBind, onClick, onLongClick)
+        fun build() = LastAdapter(list, variable, map, handler, onClick, onLongClick)

     }


     inner class ViewHolder(internal val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {

-        fun bindTo(item: Any) {
+        var mapping: Mapping<*>? = null
+
+        fun bindTo(item: Any, mapping: Mapping<*>) {
             binding.setVariable(variable, item)
             binding.executePendingBindings()

@@ -98,9 +107,18 @@ class LastAdapter private constructor(private val list: List<Any>,
                 onLongClickListener.onLongClick(item, itemView, itemViewType, adapterPosition)
                 true
             }
-            onBindListener?.onBind(item, itemView, itemViewType, adapterPosition)
+
+            mapping.onBind(item, itemView, binding, adapterPosition)
+
+            this.mapping = mapping
         }

+        fun unbind()
+        {
+            mapping?.unBind(binding)
+
+            mapping = null;
+        }
     }


@@ -116,7 +134,8 @@ class LastAdapter private constructor(private val list: List<Any>,
         return holder
     }

-    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindTo(list[position])
+    override fun onBindViewHolder(holder: ViewHolder, position: Int)
+        = holder.bindTo(list[position], map[list[position].javaClass]!!)

     override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
         if (isForDataBinding(payloads)) holder.binding.executePendingBindings()
@@ -126,7 +145,7 @@ class LastAdapter private constructor(private val list: List<Any>,
     override fun getItemCount() = list.size

     override fun getItemViewType(position: Int) = layoutHandler?.getItemLayout(list[position], position)
-            ?: map[list[position].javaClass]
+            ?: map[list[position].javaClass]?.layout
             ?: throw RuntimeException("Invalid object at position $position: ${list[position]}")

     override fun onAttachedToRecyclerView(rv: RecyclerView) {
@@ -140,6 +159,12 @@ class LastAdapter private constructor(private val list: List<Any>,
         recyclerView = null
     }

+    override fun onViewRecycled(holder: ViewHolder) {
+        super.onViewRecycled(holder)
+
+        holder.unbind()
+    }
+
     private fun addOnRebindCallback(b: ViewDataBinding, rv: RecyclerView?, pos: Int) {
         b.addOnRebindCallback(object: OnRebindCallback<ViewDataBinding>() {
             override fun onPreBind(binding: ViewDataBinding) = rv != null && rv.isComputingLayout
@@ -161,8 +186,12 @@ class LastAdapter private constructor(private val list: List<Any>,
         fun getItemLayout(item: Any, position: Int): Int
     }

-    interface OnBindListener {
-        fun onBind(item: Any, view: View, type: Int, position: Int)
+    interface OnBindListener<in T> {
+        fun onBind(item: T, view: View, binding: ViewDataBinding, position: Int)
+    }
+
+    interface OnUnbindListener {
+        fun onUnbind(binding: ViewDataBinding)
     }

     interface OnClickListener {

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

After posting that, the unbind should probably also pass the view.

It seems with this also that the click listeners are not really necessary as you can just use the bind listener and set them yourself if you wanted them.

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

I'm aware you do not make pull requests on GitHub but, honestly, I don't know how to apply a patch file. Could you copy/paste the whole file here or somewhere else so I can take a look at it more easily?

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

Wasn't even that. I recently did a PR for RxJava, just didn't have time to
do anything more and it was more for you to review to see if it made sense
to you.

On Sun, Oct 9, 2016 at 1:46 AM, Miguel รngel Moreno <
[email protected]> wrote:

I'm aware you do not make pull requests on GitHub but, honestly, I don't
know how to apply a patch file. Could you copy/paste the whole file here or
somewhere else so I can take a look at it more easily?

โ€”
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#6 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAVHzjAqon2wm-bPgqit6hjXXezLwGxeks5qyH-lgaJpZM4KPe04
.

Dale King

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

Well... It looks fine but my main concern is what I called the LayoutHandler (the capacity to choose different layouts depending on the position of the item in the adapter or any internal status of the item itself).

Setting the OnBindListener, for example, while using the LayoutHandler doesn't seem to be supported with the modifications you made. Sure it would be possible to do but I can't see it happening without adding extra complexity than just the method returning the layout id. So we would be adding an additional way of doing something that can already be done at the expense of the LayoutHandler, which I find it quite useful.

In my opinion, the map methods are very handy and have to be there as you will be "mapping" classes to layouts pretty often. But the key element here (to support the LayoutHandler as well) should be the layout id, not the item class. And with the layout id being an integer and not a class, the needed generics to generate listeners with different signatures (for different item types) doesn't seem to be possible.

The solution could be passing also the specific binding class for each type to use it for the generics thing but, again, it would kind of "ruin" the simplicity of the current LayoutHandler (its method should then return the layout id AND the binding class for this layout).

A good combination of all those possibilities passing the specific binding class or using the default ViewDataBinding when not useful/needed could be great, but it should be very well thought.

I'd like to know your opinion.

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

Hello @dalewking

I've been working on a solution similar to what you posted. I've done some modifications so the item and its binding are both cast to their appropriate classes in the listeners, except for the OnUnbindListener. The onViewRecycled(holder: ViewHolder) method in RecyclerView doesn't even provide the position of the view that is being recycled so it doesn't seem easy, if possible, to cast the item and the binding to their appropriate classes. I don't find using the binding as just ViewDataBinding so useful.

Can you tell me some good cases where having this OnUnbindListener would be useful? What kind of resources should be released there?

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

In the code I was looking at converting, I have type specific view holder classes for the various types. There are 2 variables bound to the layout, the item and the holder itself. The holder is used as the target of various event methods. In reality, the unbind handling just does:

        binding.setVariable(BR.holder, null);
        binding.executePendingBindings();

So ViewDataBinding is enough in my case.

This list in particular maintains a list of timers and alarms. Some of them have a text view that counts down the time so the text keeps updating. Setting the holder to null will cause the count down text view to quit counting down.

from lastadapter.

dalewking avatar dalewking commented on May 17, 2024

This list in particular maintains a list of timers and alarms. Some of them have a text view that counts down the time so the text keeps updating. Setting the holder to null will cause the count down text view to quit counting down.

I actually, I double checked and I was wrong about that. My unbind is just setting the holder variable to null on the layouts which unregisters the listeners from the views is all the unbind is really doing.

from lastadapter.

nitrico avatar nitrico commented on May 17, 2024

LastAdapter 2.0 will include specific listeners for each type and the onUnbind callback (probably onRecycle).

Please, see #10

from lastadapter.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.