Creating a custom Pulsate Inbox

The PuslateSDK also allows Developers to directly access the Pulsate Feed API to get the Pulsate Feed Models and build them in a custom way.

To get the Feed Models just call this method "void getFeed(int page, IPulsateValueListener listener)". This method takes two arguments - "page" which specifies which page of the Feed do you want to get and "listener" which is the listener to which we send the Feed Models and any errors if they occur. The models that are returned are - PulsateInboxItemCard and PulsateInboxItemTalk. Having all these models you can build a custom Feed.

val pulsateManager = PulsateFactory.getInstance()
pulsateManager.getFeed(1, object : IPulsateValueListener<PulsateFeedState> {
    override fun onSuccess(feedState: PulsateFeedState) {
        mAdapter.setCustomFeed(feedState.mInboxItems)
    }

    override fun onError(e: Throwable) {
        e.printStackTrace()
    }
})

Showing the Feed in a custom way also means that item clicks also need to be handled in a custom way. When handling on click events you must pass those clicks also to the PulsateSDK for it to be able to open the proper destination (deeplink, url, back of card, feed, reply) and also to send open event and custom events. To pass the clicks use the "void handleFeedClick(PulsateInboxItem pulsateInboxItem, int destination)" method.

mBinding!!.cardView.setOnClickListener {
    val pulsateManager = PulsateFactory.getInstance()
    pulsateManager.handleFeedClick(itemCard, PulsateUtils.PulsateCardDestination.VIEW_MORE);
}

Example:

<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/custom_feed_recycler"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.cardview.widget.CardView
            android:id="@+id/card_view"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginBottom="16dp"
            app:cardBackgroundColor="@color/colorAccent"
            app:cardCornerRadius="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/card_view_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:id="@+id/card_view_title"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginLeft="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:layout_marginRight="16dp"
                    android:text="TextView"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

                <TextView
                    android:id="@+id/card_view_body"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginLeft="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:layout_marginRight="16dp"
                    android:layout_marginBottom="16dp"
                    android:text="TextView"
                    app:layout_constraintBottom_toTopOf="@+id/card_view_reply"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/card_view_title" />

                <Button
                    android:id="@+id/card_view_reply"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:text="Reply"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toStartOf="@+id/card_view_view_more"
                    app:layout_constraintHorizontal_bias="0.5"
                    app:layout_constraintStart_toStartOf="parent" />

                <Button
                    android:id="@+id/card_view_view_more"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:text="View More"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.5"
                    app:layout_constraintStart_toEndOf="@+id/card_view_reply" />

            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class CustomFeedFragment : Fragment() {
    private var mBinding: FragmentCustomFeedBinding? = null
    private var mAdapter: CustomFeedRecyclerAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mAdapter = CustomFeedRecyclerAdapter()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        mBinding = FragmentCustomFeedBinding.inflate(inflater, container, false)
        setupRecyclerView()
        return mBinding!!.root
    }

    override fun onStart() {
        super.onStart()
        val pulsateManager = getInstance()
        pulsateManager.getFeed(1, object : IPulsateValueListener<PulsateFeedState> {
            override fun onSuccess(feedState: PulsateFeedState) {
                mAdapter?.setCustomFeed(feedState.mFeedItems)
            }

            override fun onError(e: Throwable) {
                e.printStackTrace()
            }
        })
    }

    private fun setupRecyclerView() {
        mBinding!!.customFeedRecycler.layoutManager = GridLayoutManager(context, 1)
        mBinding!!.customFeedRecycler.adapter = mAdapter
    }
}
class CustomFeedRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder?>() {
    private val mInboxItems: MutableList<PulsateFeedItem> = ArrayList()
    fun setCustomFeed(newPulsateInboxItems: List<PulsateFeedItem>?) {
        mInboxItems.clear()
        mInboxItems.addAll(newPulsateInboxItems!!)
        notifyDataSetChanged()
    }

    override fun getItemViewType(position: Int): Int {
        if (mInboxItems[position] is PulsateFeedItemCard) {
            return INBOX_ITEM_CARD
        } else if (mInboxItems[position] is PulsateFeedItemTalk) {
            return INBOX_ITEM_TALK
        }
        return INBOX_ITEM_NONE
    }

    override fun getItemCount(): Int {
        return mInboxItems.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            INBOX_ITEM_CARD -> {
                val itemBinding: FragmentCustomFeedItemBinding =
                    FragmentCustomFeedItemBinding.inflate(layoutInflater, parent, false)
                FragmentCustomFeedItemCardViewHolder(itemBinding)
            }
            INBOX_ITEM_TALK -> {
                val itemBinding: FragmentCustomFeedItemBinding =
                    FragmentCustomFeedItemBinding.inflate(layoutInflater, parent, false)
                FragmentCustomFeedItemTalkViewHolder(itemBinding)
            }
            else -> {
                val itemBinding: FragmentCustomFeedItemBinding =
                    FragmentCustomFeedItemBinding.inflate(layoutInflater, parent, false)
                FragmentCustomFeedItemTalkViewHolder(itemBinding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder.itemViewType) {
            INBOX_ITEM_CARD -> {
                val itemCard: PulsateFeedItemCard = mInboxItems[position] as PulsateFeedItemCard
                (holder as FragmentCustomFeedItemCardViewHolder).bind(itemCard)
            }
            INBOX_ITEM_TALK -> {
                val itemTalk: PulsateFeedItemTalk = mInboxItems[position] as PulsateFeedItemTalk
                (holder as FragmentCustomFeedItemTalkViewHolder).bind(itemTalk)
            }
        }
    }

    private class FragmentCustomFeedItemCardViewHolder(binding: FragmentCustomFeedItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val mBinding: FragmentCustomFeedItemBinding

        init {
            mBinding = binding
        }

        fun bind(itemCard: PulsateFeedItemCard) {
            mBinding.cardViewReply.setOnClickListener { view ->
                val pulsateManager = getInstance()
                pulsateManager.handleFeedClick(itemCard, PulsateUtils.PulsateCardDestination.REPLY)
            }
            mBinding.cardViewViewMore.setOnClickListener { view ->
                val pulsateManager = getInstance()
                pulsateManager.handleFeedClick(
                    itemCard,
                    PulsateUtils.PulsateCardDestination.VIEW_MORE
                )
            }
            for (i in 0 until itemCard.front.size) {
                val frontElement: PulsateCardFrontBack = itemCard.front[i]
                if (frontElement.type == "headline") {
                    val headlineAttrs = frontElement.attrs[0] as PulsateCardHeadlineAttrs
                    mBinding.cardViewTitle.text = headlineAttrs.text
                } else if (frontElement.type == "text") {
                    val textAttrs = frontElement.attrs[0] as PulsateCardHeadlineAttrs
                    mBinding.cardViewBody.text = textAttrs.text
                }
            }
        }
    }

    private class FragmentCustomFeedItemTalkViewHolder(binding: FragmentCustomFeedItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val mBinding: FragmentCustomFeedItemBinding

        init {
            mBinding = binding
        }

        fun bind(itemTalk: PulsateFeedItemTalk) {
            mBinding.cardViewReply.setOnClickListener { view ->
                val pulsateManager = getInstance()
                pulsateManager.handleFeedClick(itemTalk, PulsateUtils.PulsateCardDestination.REPLY)
            }
            val lastMessage: PulsateCardLastMessage = itemTalk.lastMessages[0]
            mBinding.cardViewTitle.text = lastMessage.sender
            mBinding.cardViewBody.text = lastMessage.content
        }
    }

    companion object {
        const val INBOX_ITEM_NONE = 0
        const val INBOX_ITEM_CARD = 1
        const val INBOX_ITEM_TALK = 2
    }
}