LazyRecycler-LiveData

Indistinct artifact id, use lazyrecycler-livedata instead

License

License

Categories

Categories

Data
GroupId

GroupId

io.github.dokar3
ArtifactId

ArtifactId

livedata
Last Version

Last Version

0.1.1
Release Date

Release Date

Type

Type

aar
Description

Description

LazyRecycler-LiveData
Indistinct artifact id, use lazyrecycler-livedata instead
Project URL

Project URL

https://github.com/dokar3/LazyRecycler
Source Code Management

Source Code Management

https://github.com/dokar3/LazyRecycler

Download livedata

How to add to project

<!-- https://jarcasting.com/artifacts/io.github.dokar3/livedata/ -->
<dependency>
    <groupId>io.github.dokar3</groupId>
    <artifactId>livedata</artifactId>
    <version>0.1.1</version>
    <type>aar</type>
</dependency>
// https://jarcasting.com/artifacts/io.github.dokar3/livedata/
implementation 'io.github.dokar3:livedata:0.1.1'
// https://jarcasting.com/artifacts/io.github.dokar3/livedata/
implementation ("io.github.dokar3:livedata:0.1.1")
'io.github.dokar3:livedata:aar:0.1.1'
<dependency org="io.github.dokar3" name="livedata" rev="0.1.1">
  <artifact name="livedata" type="aar" />
</dependency>
@Grapes(
@Grab(group='io.github.dokar3', module='livedata', version='0.1.1')
)
libraryDependencies += "io.github.dokar3" % "livedata" % "0.1.1"
[io.github.dokar3/livedata "0.1.1"]

Dependencies

compile (3)

Group / Artifact Type Version
org.jetbrains.kotlin : kotlin-stdlib jar 1.4.31
androidx.lifecycle » lifecycle-livedata-ktx jar 2.3.0
io.github.dokar3 : lazyrecycler jar 0.1.1

Project Modules

There are no modules declared in this project.

RecyclerView,Compose like

Maven Central

LazyRecycler is a library that provides LazyColumn (from Jetpack Compose) like APIs to build lists with RecyclerView.

Usage

implementation 'io.github.dokar3:lazyrecycler:0.1.7'

With LazyRecycler, a few dozen lines of code can do almost all things for RecyclerView. Adapter, LayoutManager, DiffUtil, OnItemClickListener and more, these are all in one block:

LazyRecycler(recyclerView, spanCount = 3) {
    item(R.layout.header, Unit) {}

    items(listOfNews) { binding: ItemNewsBinding, news ->
        // bind item
    }.clicks { view, item ->
        // handle item clicks
    }.spanSize { position ->
        // map to SpanSizeLookup.getSpanSize()
        if (position % 3 == 0) 3 else 1
    }.differ {
        areItemsTheSame { oldItem, newItem ->
            // map to DiffUtil.ItemCallback.areItemsTheSame()
            oldItem.id == newItem.id
        }
        areContentsTheSame { oldItem, newItem ->
            // map to DiffUtil.ItemCallback.areContentsTheSame()
            oldItem.title == newItem.title && ...
        }
    }
    ...
    item(R.layout.footer, Unit) {}
}

Basic

Sections

LazyRecycler uses sections to build lists, A section will contain id, items, view creator, view binder, click listeners and other needed properties, it represents a view type in adapter. To create a new section, use the item and items function:

item(...) { ... }

items(...) { ... }

Sections should only be used in LazyRecycler / newSections block, so it's necessary to pass a unique id when creating a dynamic section (Require doing some operations after list is created, like update, remove, hide and show). Or use mutable data sources (see the section below).

val lazyRecycler = LazyRecycler {
    items(..., sectionId = SOME_ID) { ... } 
}

// update
lazyRecycler.updateSection(SOME_ID, items)
// remove
lazyRecycler.removeSection(SOME_ID)
// hide and show
lazyRecycler.setSectionVisible(SOME_ID, false)

Layout

layout id and ViewBinding/DataBinding are supported:

items(R.layout.item_news, news) { ... }

items(news) { binding: ItemNewsBinding, item -> ... }

Providing an item view in the bind scope is also supported:

items(news) { parent ->
    val itemView = CustomNewsItemView(context)
    ...
    return@item itemView
}

Bind

For ViewBinding item/items:

items(news) { binding: ItemNewsBinding, item -> 
    binding.title.text = item.title
    binding.image.load(item.cover)
    ...
}

For DataBinding item/items:

items(news) { binding: ItemNewsDataBinding, item -> 
    binding.news = item
}

For layoutId item/items:

items(R.layout.item_news, news) { view ->
    ...
    val tv: TextView = view.findViewById(R.id.title)
    bind { item ->
        tv.text = item.title
    }
}

For view required item/items:

items(news) { parent -> 
    val itemView = CustomNewsItemView(context)
    bind { item ->
        itemView.title(item.title)
        ...
    }
    return@item itemView
}

Generics

All the item, items, and template are generic functions:

items<I>(layoutId, I) { ... }
items<V, I>(items) { ... }
items<I>(items) { ... }
  • I for item type
  • V for the View Binding

Overloads

There are a lots of item/items functions to support various layouts, for helping compiler to pick correct one, lambdas may require explicit argument(s):

// item
item(R.layout.item, Any()) {
	// Parameter 'view' can be ommited
}
item(Any()) { binding: ItemBinding, item -> 
}
item(Any()) { parent -> 
    // Parameter 'parent' is required to distinguish from view binding one
    // Replace with '_' if it's unused
    return@item TextView(context) 
}

// items
items(R.layout.item, listOf(Any()) {
    // Parameter 'view' can be ommited
}
items(listOf(Any()) { binding: ItemBinding, item -> 
}
items(listOf(Any()) { parent -> 
    // Parameter 'parent' is required to distinguish from view binding one
    // Replace with '_' if it's unused
    return@items TextView(context) 
}

LayoutManager

LazyRecycler create a LinearLayoutManager by default, if spanCount is defined, GridLayoutManager will be picked. To skip the LayoutManager setup, set setupLayoutManager to false:

LazyRecycler(
    recyclerView,
    setupLayoutManager = false,
    isHorizontal = false, // ignored
    spanCount = 3, // ignored
    reverseLayout = false, // ignored
    stackFromEnd = false, // ignored
) { ... }
....
recyclerView.layoutManager = ...

spanSize() is used to define SpanSizeLookup for GridLayoutManager:

item(...) {
}.spanSize {
    3
}

items(...) {
    ...
}.spanSize { position ->
    if (position == 0) 3 else 1
}

DiffUtil

Use differ function, then LazyRecycler will do the all works after section items changed.

items(...) {
    ...
}.differ {
    areItemsTheSame { oldItem, newItem ->
        ...
    }
    areContentsTheSame { oldItem, newItem ->
        ...
    }
}

Clicks

clicks for the OnItemClickListener, longClicks for the OnItemLongClickListener.

items(...) {
    ...
}.clicks { view, item -> 
    ...
}.longClicks { view, item -> 
    ...
}

Mutable data sources

To support some mutable(observable) data sources like Flow, LiveData, or RxJava, add the dependencies:

// Flow
implementation 'io.github.dokar3:lazyrecycler-flow:0.1.7'
// LiveData
implementation 'io.github.dokar3:lazyrecycler-livedata:0.1.7'
// RxJava3
implementation 'io.github.dokar3:lazyrecycler-rxjava3:0.1.7'

Flow

  1. Use extension function Flow.asMutSource(CoroutineScope):

    import com.dokar.lazyrecycler.flow.asMutSource
    
    val entry: Flow<I> = ...
    // Use items() instead of item()
    items(entry.asMutSource(coroutineScope)) { ... }
    
    val source: Flow<List<I>> = ...
    items(source.asMutSource(coroutineScope)) { ... }
  2. Observe/stop observing:

    val lazyRecycler = LazyRecycler { ... }
    ...
    // Not needed, Observe data changes, will be called automatically
    lazyRecycler.observeChanges()
    // Stop observing, Not needed if mutable data sources are created from 
    // a lifecycleScope
    lazyRecycler.stopObserving()

LiveData

  1. Use extension function LiveData.asMutSource(LifecycleOwner):

    import com.dokar.lazyrecycler.livedata.asMutSource
    
    val entry: LiveData<I> = ...
    // Use items() instead of item()
    items(entry.asMutSource(lifecycleOwner)) { ... }
    
    val source: LiveData<List<I>> = ...
    items(source.asMutSource(lifecycleOwner)) { ... }
  2. Observe/stop observing:

    // Not needed, lifecycle will remove observers when components are destroyed

RxJava

  1. Use extension function Observable.asMutSource():

    import com.dokar.lazyrecycler.rxjava3.asMutSource
    
    val entry: Observable<I> = ...
    // Use items() instead of item()
    items(entry.asMutSource()) { ... }
    
    val source: Observable<List<I>> = ...
    items(source.asMutSource()) { ... }
  2. Observe/stop observing:

    val lazyRecycler = LazyRecycler { ... }
    ...
    // Not needed, Observe data changes, will be called automatically
    lazyRecycler.observeChanges()
    // Call it when Activitiy/Fragment is destroyed
    lazyRecycler.stopObserving()

Advanced

Alternate sections

template() + items(section, ...): A section can be created from a Template:

LazyRecycler {
    // layout id definition
    val sectionHeader = template<String>(R.layout.section_header) {
        // bind item
    }
    // ViewBinding definition
    val normalItem = template<ItemViewBinding, Item> { binding, item ->
        // bind item
    }
    
    item(sectionHeader, "section 1")
    items(normalItem, source)
    
    item(sectionHeader, "section 2")
    items(normalItem, source)
    ...
}

Single items with multiple view types

template() + subSection(): Multiple view types for single data source is supported:

LazyRecycler {
    val bubbleFromMe = template<ItemMsgFromMeBinding, Message> { binding, msg ->
        // bind item
    }
    
    items(messages) { binding: ItemMsgBinding, msg ->
        // bind item
    }.subSection(bubbleFromMe) { msg, position ->
        // condition
        msg.isFromMe
    }
    ...
}

Add new sections dynamically

LazyRecycler.newSections():

val lazyRecycler = ...
...
lazyRecycler.newSections {
    item(...) { ... }
    items(...) { ... }
    ...
}
// If new sections contain any observable data source
lazyRecycler.observeChanges()

Build list in background

LazyRecycler.attchTo():

backgroundThread {
    val recycler = LazyRecycler {
        ...
    }
    uiThread {
        lazyRecycler.attchTo(recyclerView)
    }
}

ProGuard

If ViewBinding item/items are used, add this rule to proguard file:

# keep ViewBinding.inflate for LazyRecycler
-keepclassmembers class * implements androidx.viewbinding.ViewBinding {
    inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
}

Demos

1. Multiple sections

Day Night

2. Chat screen

Day Night

3. Tetris game (Just for fun)

Day Night

Links

License

Apache License 2.0

Versions

Version
0.1.1
0.1.0