All Projects → justeat → jubako

justeat / jubako

Licence: other
A small API to help display rich content in a RecyclerView such as a wall of carousels

Programming Languages

kotlin
9241 projects
java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to jubako

MultiTypeAdapter
RecyclerView通用多类型适配器MultiTypeAdapter,以布局文件为单位更细粒度的条目复用。
Stars: ✭ 18 (-35.71%)
Mutual labels:  recyclerview, recyclerview-adapter
LxAdapter
RecyclerView Adapter Library
Stars: ✭ 50 (+78.57%)
Mutual labels:  recyclerview, recyclerview-adapter
Modular2Recycler
Modular²Recycler is a RecyclerView.Adapter that is modular squared.
Stars: ✭ 72 (+157.14%)
Mutual labels:  recyclerview, recyclerview-adapter
RvClickListenerExample
Example showing the implementation of onItemClickListener & getAdapterPosition() in RecyclerView.
Stars: ✭ 22 (-21.43%)
Mutual labels:  recyclerview, recyclerview-adapter
recyclerview-kotlin
10+ Tutorials on RecyclerView in Kotlin
Stars: ✭ 41 (+46.43%)
Mutual labels:  recyclerview, recyclerview-adapter
BaseRecyclerViewAdapter
RecyclerView通用适配器
Stars: ✭ 14 (-50%)
Mutual labels:  recyclerview, recyclerview-adapter
AdapterCommands
Drop in solution to animate RecyclerView's dataset changes by using command pattern
Stars: ✭ 74 (+164.29%)
Mutual labels:  recyclerview, recyclerview-adapter
Async Expandable List
Stars: ✭ 221 (+689.29%)
Mutual labels:  recyclerview, recyclerview-adapter
slush
This library will no longer be updated 😭
Stars: ✭ 26 (-7.14%)
Mutual labels:  recyclerview, recyclerview-adapter
PrimeAdapter
PrimeAdapter makes working with RecyclerView easier.
Stars: ✭ 54 (+92.86%)
Mutual labels:  recyclerview, recyclerview-adapter
recyclerview-adapters
Multiple item adapters for RecyclerView (inspired by Merge Adapter)
Stars: ✭ 24 (-14.29%)
Mutual labels:  recyclerview, recyclerview-adapter
Statik
A simple static list information backed by RecyclerView for Android in Kotlin
Stars: ✭ 22 (-21.43%)
Mutual labels:  recyclerview, recyclerview-adapter
Adapterdelegates
"Favor composition over inheritance" for RecyclerView Adapters
Stars: ✭ 2,735 (+9667.86%)
Mutual labels:  recyclerview, recyclerview-adapter
InfiniteScrollRecyclerView
Enables the RecyclerView to Auto scroll for indefinite time.
Stars: ✭ 49 (+75%)
Mutual labels:  recyclerview, recyclerview-adapter
Admobadapter
It wraps your Adapter to display Admob native ads and banners in a ListView/RecyclerView data set. It based on the Yahoo fetchr project https://github.com/yahoo/fetchr
Stars: ✭ 224 (+700%)
Mutual labels:  recyclerview, recyclerview-adapter
recyclerview-expandable
RecyclerView implementation of traex's ExpandableLayout
Stars: ✭ 70 (+150%)
Mutual labels:  recyclerview, recyclerview-adapter
Moretype
new method to build data in RecyclerView with Kotlin!
Stars: ✭ 189 (+575%)
Mutual labels:  recyclerview, recyclerview-adapter
Flap
Flap(灵动),一个基于 RecyclerView 的页面组件化框架。
Stars: ✭ 204 (+628.57%)
Mutual labels:  recyclerview, recyclerview-adapter
Recycling
A Library for make an easy and faster RecyclerView without adapter
Stars: ✭ 57 (+103.57%)
Mutual labels:  recyclerview, recyclerview-adapter
SortedListAdapter
The RecyclerView.Adapter that makes your life easy!
Stars: ✭ 48 (+71.43%)
Mutual labels:  recyclerview, recyclerview-adapter

Android Arsenal

Alt text

Movies Sample

Jubako makes things super simple to assemble rich content into a RecyclerView such as a wall of carousels (Google Play style recycler in recyclers). Jubako can load content on the fly asynchronously, infinitely with pagination.

The simplest example - "Hello Jubako! x 100"

class HelloJubakoActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_jubako_recycler)

        recyclerView.withJubako(this).load {
            for (i in 0..100) {
                addView { textView("Hello Jubako!") }
                addView { textView("こんにちはジュバコ") }
            }
        }
    }

    private fun textView(text: String): TextView {
        return TextView(this).apply {
            setText(text)
        }
    }
}

In this simple example we use some of Jubako's convenience extensions to compose a RecyclerView with 100 rows.

Firstly the extension function RecyclerView.withJubako expresses which RecyclerView we want to load into (passing context) and then we follow up with a call to load describing what we want to load inside its lambda argument.

We can then make calls to Jubako's withView extension function to specify each view (just a regular android.view.View) we wish to display for a row in our recycler that conveniently constructs the necessary boilerplate under the hood.

In the example we just print out 100 rows of static content, but Jubako was built to do much more than that.

Mostly this approach might work for simple applications, but under the hood Jubako offers more verbose construction to support more complicated scenarios.

The best place to start right now with Jubako is to check it the examples in the jubako-sample app in this repository.

Documentation is rough right now so the best place to begin would be to run & study the examples https://github.com/justeat/jubako/tree/master/jubako-sample

Gradle Dependencies

Add the following repository to your root gradle file

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }

    }
}

Add the following dependencies to your project gradle file

implementation 'com.github.justeat.jubako:jubako:0.61'
implementation 'com.github.justeat.jubako:jubako-recyclerviews:0.61'

Describing content

For each row in Jubako is a ContentDescription that defines which view holder to use and which data to bind (in the form of LiveData<T>).

With Jubako, when we assemble content for display using a ContentAssembler, we provide this content as a list of ContentDescription or more precisely a list of ContentDescriptionProvider where a providers purpose is to produce a description.

The following example shows a basic implementation of a ContentDescriptionProvider.

class HelloContentDescriptionProvider(private val language: Language) : ContentDescriptionProvider<String> {
    enum class Language { ENGLISH, JAPANESE }
    
    private val service = HelloService()
    
    override fun createDescription(): ContentDescription<String> {
        return ContentDescription(
            data = when (language) {
                ENGLISH -> service.getHelloEnglish()
                JAPANESE -> service.getHelloJapanese()
            },
            viewSpec = HelloViewHolderFactory()
        )
    }
}

A description has various properties of which some are required.

id: String (optional)

A unique ID that represents this row (use UUID to create one to keep things unique if a specific name is not important)

viewSpec: Any (required)

A JubakoAdapter.HolderFactory<T> factory class that will create a ViewHolder that you want to use when rendering (the type is currently Any to possibly support other UI frameworks such as Jetpack Compose

data: LiveData? (required)

The data that will be loaded where T can be any type, later on when rendering this data (when loaded) will be passed to your ViewHolder's bind(T) that you can implement to render the loaded content.

Live Data Usage

Jubako makes use of LiveData<T>, the way in which you should provide your data from ContentDescription<T>::data.

When Jubako observes your data it expects you to postValue when your data is ready, the simplest implementation could be:-

ContentDescription(
    viewSpec = TestViewHolderFactory(),
    data = object : LiveData<String>() {
        override fun onActive() {
            thread {
                postValue("Hello, Jubako!")
            }.run()
        }
    })

In the example, as soon as our first observer observes data, we postValue("Hello, Jubako!") and in doing so your corresponding JubakoViewHolder will recieve the data in a call to bind(...).

class ExampleViewHolder(view: View) : JubakoViewHolder<String>(view) {
    override fun bind(data: String?) {
        // do something with data, like bind it to views (should be:- Hello Jubako!)
    }
}

You should always postValue regardless, because if you do not, then Jubako will think it needs to wait, and will wait currently wait for ever. The best strategy to employ would be to catch your exceptions and always deliver a result to postValue(...)

In an enterprise implementation it would be common to assign data with the result of repository method with live data.

ContentDescription(
    viewSpec = TestViewHolderFactory(),
    data = repository.getData()
    })

Assembling content

Without the added convenience of Jubako load we can also load content with a derived implementation of JubakoAssembler.

An assembler (similar to an adapter) is used to compose a list of descriptions that we wish to render (carousels, cards, etc). Its basic interface has a single function ::assemble() that will be called by Jubako when it is time to assemble this list.

Content is added with the assembler by creating and adding instances of ContentDescriptionProvider, and the purpose of a provider is to construct an instance of ContentDescription where a content description defines which view holder to use and the data that will be bound to the view holder where that data is a LiveData<T>.

The simplest usage of JubakoAssembler is using its derived type SimpleJubakoAssembler that adds convenience to assembling simple lists of content, for example:-

val assembler = SimpleJubakoAssembler {
    add(HelloDescriptionProvider())
}

Jubako will call JubakoAssembler::assemble() asynchronously (via coroutines) and this will give your implementation the chance to perform initialisation work such as fetching data in order to construct this list of descriptions.

You can tell Jubako that your assembler produces even more content if ::assemble is called again by implementing JubakoAssembler::hasMore you can control how much more content you want Jubako to consume by returning true or false for more or no more content respectively.

Jubako's OOTB PaginatedContentLoadingStrategy will take care of loading more when demanded.

Waiting for assembly to complete

When Jubako calls JubakoAssembler::assemble it will do so asynchronously which we refer to as the Assembly Phase

During assembly you can respond to state changes from Assembling to Assembled and AssembleError

The first state Assembling tells you that your JubakoAssembler is currently waiting for assemble to return before it goes into the Assembled state. You can respond to these state changes when observing content as follows:-

Jubako.observe(this) { state ->
    when (state) {
        is Jubako.State.Assembled -> {
            recyclerView.adapter = JubakoAdapter(state.data)
        }
        is Jubako.State.Assembling -> {
            // TODO show a loading indicator
        }
        is Jubako.State.AssembleError -> {
            // TODO deal with the error
        }
    }
}

In the example above the common case for listening to Assembling is to show or hide loading indicators and handle any exceptions from the call to ::assemble().

Although Assembled state will indicate the assembly phase completed, it may not be the best time to display content. As well as Jubako having the flexibility of an asynchronous assembly phase, once assembled, Jubako will proceed to fill up the screen with content by loading descriptions one by one filling down the screen and this could take time depending on what you assigned to each ContentDescription::data property - it would therefore be best to know when the screen is filled and can you do that by setting JubakoAdapter::onInitialFill described in the next section.

JubakoAdapter

Once you observe the state Jubako.State.Assembled you can go ahead and construct your JubakoAdapter, by default the adapter will use PaginatedContentLoadingStrategy.

If you use JubakoRecyclerView then you will not need to set a layout manager (and not a good idea either since Jubako currently supports only LinearLayoutManager in vertical orientation).

Jubako.observe(this) { state ->
    when (state) {
        is Jubako.State.Assembled -> {
            jubakoRecycler.adapter = JubakoAdapter(activity, state.data)
        }
    }
}

Initial Fill

JubakoAdapter will invoke a callback onInitialFill that will when Jubako initially fills the screen with content. This callback can also be used to hide any loading indicatorsand show the content (the RecyclerView). The difference between this callback and Jubako.State.Assembledis that it occurs after data has loaded and the screen is filled for the first time where Jubako.State.Assembled when data is first loaded.

If any of your content descriptions have live data that takes some time to load it may be more appropriate to wait for the screen to fill by hooking into onInitialFill before transitioning from loading indicators to showing content.

Reloading

sometimes you might need to reload a ContentDescription, you can do this from either within the JubakoViewHolder or from the JubakoAdapter

Reloading from JubakoViewHolder

The following example shows how you can call the reload() function from within a JubakoViewHolder.

class ExampleViewHolder(view: View) : JubakoViewHolder<String>(view) {
    override fun bind(data: String?) {
        itemView.findViewById(R.id.error_button).setOnClickListener {
            reload()
        }
    }
}

Reloading from JubakoAdapter

Reloading from JubakoAdapter requires the id you assigned to the ContentDescription when creating a given description, once this is done you can call reload, eg:-

jubakoAdapter.reload(SOME_UNIQUE_ID)

You can also reload with some arbitrary data (payload) that will be passed onto the onReload function of ContentDescription (see onReload in next section)

jubakoAdapter.reload(SOME_UNIQUE_ID, "Hello, World!")

Handling a reload

Although you can call reload on the JubakoAdapter or JubakoViewHolder, you still need to handle what happens when its called. You must implement the function ContentDescription::onReload which in simplest case reassigns ContentDescription::data with a new LiveData<T> as follows:-

ContentDescription(
    id = SOME_UNIQUE_ID,
    viewSpec = TestViewHolderFactory(),
    data = object : LiveData<String>() {
        override fun onActive() {
            thread {
                postValue("Initial value")
            }.run()
        }
    },
    onReload = { payload ->
        data = object : LiveData<String>() {
            override fun onActive() {
                thread {
                    postValue("Peek-a-Boo! $payload")
                }.run()
            }
        }
    })

The example shows that onReload provides a function that reassigns data, JubakoAdapter will effectively call this before it observes data again.

JubakoViewHolder events

It is possible to propagate events from a JubakoViewHolder to JubakoAdapter where integrations can listen for events by providing a callback function to JubakoAdapter::onViewHolderEvent

First we need to fire an event from the JubakoViewHolder, eg:-

class ExampleViewHolder(view: View) : JubakoViewHolder<String>(view) {
    init {
        itemView.findViewById(R.id.hello_button).apply {
            setOnClickListener {
                postClickEvent(R.id.hello_button)
            }
        }
    }
}

Then later we hook into JubakoAdapter and respond to the event:-

adapter.onViewHolderEvent = {
    when (it) {
        is JubakoViewHolder.Event.Click -> {
            when(it.viewId) {
                R.id.hello_button -> showMessage("Hello!")
            }
        }
    }
}

Resetting

In order to maintain state across configuration changes making another call to content.load(JubakoAssembler) will do nothing unless you call jubako.reset() beforehand.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].