• Stars
    star
    108
  • Rank 321,259 (Top 7 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created almost 9 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Composable adapters for Android RecyclerViews and ListViews

Table of Contents

Power Adapters

Presenting large data sets efficiently can be a challenging part of Android development. It gets more complicated as we begin to handle edge cases and add additional decorations like headers. We also often find ourselves repeating undesirable boilerplate as we write adapters for each data source. In addition, Android doesn't provide a clean object-oriented, reusable way of presenting collections of multiple types.

Feature Summary

This library provides the following features:

  • Present multiple data types within an adapter in a type-safe manner
  • Concatenate multiple adapters together
  • Show headers and footers
  • Show a loading indicator to indicate a loading state
  • Show an empty item to indicate an empty underlying data set
  • Add dividers in between items of an existing adapter
  • Show an adapter or item range only when a condition evaluates to true
  • Present nested adapters, a powerful substitute for ExpandableListView without any limitation of nesting level
  • Load from remote or slow data sources asynchronously
  • Backed up by unit tests, verifying the correct notifications are issued and state maintained
  • Minimal dependencies; doesn't include any unnecessary transitive dependencies
  • All adapters issue the correct insertion/removal/change notifications needed for full RecyclerView animation support
  • Kotlin extension modules, which add idiomatic Kotlin APIs
  • RxJava extension modules, adding easy integration with Observables, etc

Power adapters are compatible with the following collection view classes:

  • androidx.recyclerview.widget.RecyclerView
  • android.widget.ListView
  • android.widget.GridView
  • android.support.v4.view.ViewPager
  • Any other view that accepts a android.widget.Adapter

Usage

Get it from Maven Central, using Gradle:

implementation 'com.nextfaze.poweradapters:power-adapters:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-recyclerview-v7:0.26.0'

Basic

// Declare a binder for your item type
class TweetHolder extends ViewHolder {

    TextView textView;

    TweetHolder(View view) {
        super(view);
        textView = (TextView) view.findViewById(R.id.text);
    }
}

Binder<Tweet, View> tweetBinder = 
        ViewHolderBinder.create(R.layout.tweet, TweetHolder::new, (container, tweet, tweetHolder, holder) -> {
    tweetHolder.textView.setText(tweet.getText());
});

// Construct your "core" adapter
ListBindingAdapter<Tweet> tweetsAdapter = new ListBindingAdapter<>(tweetBinder);

// Assign to your RecyclerView
recyclerView.setAdapter(RecyclerPowerAdapters.toRecyclerAdapter(tweetsAdapter));

RxJava

RxJava modules are available. Simply append -rxjava2 to get the RxJava module:

implementation 'com.nextfaze.poweradapters:power-adapters-rxjava2:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-data-rxjava2:0.26.0'

Kotlin

Kotlin modules are also provided for most modules. Append -kotlin to get the Kotlin module:

implementation 'com.nextfaze.poweradapters:power-adapters-kotlin:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-data-kotlin:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-rxjava2-kotlin:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-data-rxjava2-kotlin:0.26.0'
implementation 'com.nextfaze.poweradapters:power-adapters-recyclerview-v7-kotlin:0.26.0'

Some of the Kotlin APIs include:

  • Top-level factory functions:
    val data = data { api.getPosts() }
    val data = cursorData({ db.getUsers() }, ::User)
    val header = viewFactory<TextView>(R.layout.header) {
        text = "News"
    }
  • Extension functions:
    recyclerView.adapter = myPowerAdapter.toRecyclerAdapter()
  • PowerAdapter and Data Factory methods: adapterOf(), dataOf()
  • Binder factory methods:
    val binder = binder<Item, ItemView>(R.layout.item) { container, item, holder ->
        title = item.name
        imageUri = item.imageUri
    }
  • Operator overloads:
    adapter.showOnlyWhile(empty and !anotherThing)
    val adapter = itemsAdapter + anotherAdapter
    
    data += dataObserver { updateViews() }
  • Property delegates:
    val condition = ValueCondition()
    var enabled by condition
    val adapter = myAdapter.showOnlyWhile(condition)
    // Reassign property to control visibility of adapter
    enabled = false
  • Type-safe builder:
    adapter {
        layoutResource(R.layout.header)
        +myItemsAdapter
        layoutResource(R.layout.footer)
    }

Adapter Composition

Power Adapters can be composed by using the fluent chaining methods. For example, say you want to present a list of tweets, with a loading indicator, but show an empty message when there are no tweets, you can write the following:

PowerAdapter adapter = tweetsAdapter
    .limit(10) // Only show up to 10 tweets
    .append(
        // Show empty item while no tweets have loaded
        asAdapter(R.layout.tweets_empty_item).showOnlyWhile(noTweets()),
        // Show loading indicator while loading
        asAdapter(R.layout.loading_indicator).showOnlyWhile(tweetsAreLoading())
    )
recyclerView.setAdapter(RecyclerPowerAdapters.toRecyclerAdapter(adapter));

This lets you write a simple TweetAdapter class, the only responsibility of which is to present tweets. By using PowerAdapter.append as such, the TweetAdapter need not be modified, and can be potentially reused elsewhere more easily. The use of showOnlyWhile applies a condition to the empty footer item, so it remains hidden unless the underlying list of tweets is empty.

Headers and Footers

Headers and footers can be added using prepend and append:

// Prepend a header view.
PowerAdapter adapter = tweetAdapter.prepend(R.layout.header);
// Append a footer view.
PowerAdapter adapter = tweetAdapter.append(R.layout.footer);

Data Type Binding

Included in Power Adapters is the ability to bind elements in your data set to views in a reusable, readable, and type-safe manner.

Binder

The primary class needed to achieve this is a Binder. The responsibilities of a Binder include:

  • Construct a View to be bound, and re-used by the adapter/recycler view
  • Bind an object and/or data set index to the View

Multiple types of commonly required binders are supplied. If you prefer the widely used view holder pattern, use a ViewHolderBinder:

Binder<BlogPost, View> blogPostBinder = 
        ViewHolderBinder.create(R.layout.post, BlogPostHolder::new, (container, blogPost, blogPostHolder, holder) -> {
    blogPostHolder.labelView.setText("Blog: " + blogPost.getTitle());
});

class BlogPostHolder extends ViewHolder {

    TextView labelView;

    BlogPostHolder(View view) {
        super(view);
        labelView = (TextView) view.findViewById(android.R.id.text1);
    }
}

If you use custom views for each of your data models, use Binder.create. It takes a layout resource or a ViewFactory. The view returned by the ViewFactory is passed to subsequent bindView calls, saving you from writing a separate ViewHolder.

For example:

Binder<Tweet, TweetView> tweetBinder = Binder.create(R.layout.tweet_item, ((container, sample, v, holder) -> {
    v.setTweet(tweet);
    v.setOnClickListener(v -> onTweetClick(tweet));
}))

Mapper

The examples above have all dealt with a single item type, and so there has only been a single Binder. When you want your list to contain multiple items, a Mapper is consulted to determine which Binder to use for presenting each particular item. Typically you'll use MapperBuilder to declaratively assign your model classes to binders:

Mapper mapper = new MapperBuilder()
    .bind(Tweet.class, new TweetBinder())
    .bind(Ad.class, new AdBinder())
    .bind(Video.class, new VideoBinder())
    .build();
ListBindingAdapter<Object> adapter = new ListBindingAdapter<>(mapper);
adapter.add(new Tweet());
adapter.add(new Ad());
adapter.add(new Video());

Conversion

PowerAdapter is designed to be used with different collection view implementations, so a final step is converting it to implement the expected adapter interface. This would usually be done as soon as the collection view is created, say in onViewCreated:

recyclerView.setAdapter(toRecyclerAdapter(powerAdapter));

The following conversion methods are provided:

Collection View Converter Extension Module
ListView PowerAdapters.toListAdapter() None
RecyclerView RecyclerPowerAdapters.toRecyclerAdapter() power-adapters-recyclerview-v7
ViewPager SupportPowerAdapters.toPagerAdapter() power-adapters-support-v4

Nested Adapters

The TreeAdapter class allows you to present hierarchical data structures with no intrinsic depth limit. Each layer is comprised of just another adapter - your children can themselves can be TreeAdapters!

PowerAdapter rootAdapter = new FileAdapter(new File("/"));
TreeAdapter treeAdapter = new TreeAdapter(rootAdapter, position -> {
    // Create a child adapter for this position in the root data set.
    // Can be another TreeAdapter!
   return createChildAdapter(position);
});
treeAdapter.setExpanded(15, true);

Asynchronous Data Loading

Implementing a UI for presenting the contents of a remote collection, like a list of comments or products, requires several different mechanics. Among them are:

  • Perform requests asynchronously to avoid blocking the UI thread
  • Presenting a loading indicator to give the user feedback on progress
  • Allow the user to page through results
  • Handle and present errors as they occur
  • Dispatch change notifications to your adapter so your RecyclerView or ListView can react to content changes

The power-adapters-data extension module aims to simplify this by encapsulating the above concerns into a single object: Data<T>. In doing so, it allows you to retain one object when a config change occurs, like an orientation change. This way you don't need to reload or parcel/unparcel all of your list results when that occurs. The Data<T> object comprises much of the repetitive asynchronous UI "glue" code you'd otherwise have to write (and debug) yourself.

implementation 'com.nextfaze.poweradapters:power-adapters-data:0.26.0'

Basic Data Usage

The recommended usage pattern is to instantiate a Data<T> object in your retained Fragment. The Data.fromList factory method supports the simplest use case, fetching a list of items asynchronously:

public final class ProductListFragment extends Fragment {

    private final Data<Product> products = Data.fromList(() -> api.getProducts());

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Retain this fragment so we don't need to reload the products after a config change
        setRetainInstance(true);
    }
}

Now hook up your Data<Product> instance and a Binder with your RecyclerView:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    PowerAdapter adapter = new DataBindingAdapter(products, productBinder);
    recyclerView.setAdapter(RecyclerPowerAdapters.toRecyclerAdapter(adapter));
}

Invalidating and Reloading

At some stage you'll want to request a reload of the elements from the remote source. You can do this using reload(), refresh(), or invalidate(). The behaviour of these methods differ slightly, but ultimately they all result in your items being reloaded from the source. See the Data javadoc for how they differ.

DataLayout

DataLayout aids in presenting the various states of a Data instance, by hiding and showing contents, empty, error, and loading child views. It's a RelativeLayout subclass, and it works by accepting a Data instance, then registering to receive change notifications. If the contents are empty, your marked empty view will be shown instead of the list view. If an error occurs, the error view will be shown until a reload is triggered. DataLayout has several extension points to customize this behaviour to suite the needs of your application.

Here's an example of how to declare a DataLayout in XML. Notice the layout_component attributes:

<com.nextfaze.poweradapters.data.widget.DataLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/news_fragment_data_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

    <ListView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_component="content"/>

    <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            app:layout_component="loading"/>

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            app:layout_component="empty"
            android:text="No items!"/>

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            app:layout_component="error"
            android:textColor="#ffff0000"/>

</com.nextfaze.poweradapters.data.widget.DataLayout>

The DataLayout observes state changes of the Data to know when to update the view visibility. Connecting to your DataLayout and RecyclerView in Java code:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    PowerAdapter adapter = new DataBindingAdapter(products, productBinder);
    listView.setAdapter(RecyclerPowerAdapters.toRecyclerAdapter(adapter));
    dataLayout.setData(products);
}

RxJava Module

An RxJava module is provided: power-adapters-data-rxjava2. This is a simple adapter library that provides Observables for properties of Data:

RxData.inserts(products).subscribe(event -> handleProductInsert(event));

Data Views

Data has fluent chaining methods for providing filtered, transformed, or sorted views of its contents:

Data<String> names = ...
Data<Integer> lengths = names.transform(name -> name.length);
Data<Post> allPosts = ...
Data<Post> todaysPosts = names.filter(post -> isToday(post.getDate()));

Samples

Check the included sample project for a range of usage pattern examples.

Build

Building instructions:

$ git clone [email protected]:NextFaze/power-adapters.git
$ cd power-adapters
$ ./gradlew clean build

License

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

More Repositories

1

ionic-manup

Mandatory Update for Ionic
TypeScript
54
star
2

dev-fun

Annotation based developer targeted library. Call any function from anywhere from a nice UI or web interface.
Kotlin
51
star
3

Loopback-Style-Guide

Coding style suggestions and general 'gotchas' when working with Strongloop/Loopback
33
star
4

awesome-serverless-api

AWS Serverless API powered by Nestjs
TypeScript
18
star
5

NFSafariTabs

An implementation of Safari for iOS tabs view using UICollectionViews
Objective-C
18
star
6

devmod

Developer Module for debugging web applications
TypeScript
17
star
7

angular-rollbar

Rollbar logging service for Angular 2+
TypeScript
14
star
8

couchdbsyncer-ios

iOS library to perform a one-way sync of documents and attachments from a couchdb database to a local core data store
Objective-C
13
star
9

github-action-unity-example

Using the Kart Template, and GameCI docker images - this will release a WebGL version of the game to Github Pages
C#
12
star
10

loopback-mixin-model-changes

Track create, update and delete actions on a loopback model
JavaScript
8
star
11

instagram-graph-sdk

Simple wrapper for instagram graphql API
TypeScript
8
star
12

vscode-json-parse-stringify

Switch between stringified and parsed JSON in vscode
TypeScript
7
star
13

WordPressSyncer

syncs wordpress sites to a local core data store
Objective-C
6
star
14

flutter_manup

Mandatory Update for Flutter Apps
Dart
6
star
15

ios-manup

A server side check of the app version and configuration options for your iOS app.
Objective-C
5
star
16

NFAllocInit

The starting point for an iOS app - helper classes and the like
Objective-C
5
star
17

jng-image

JNG image reader for iOS
C
4
star
18

diy-comic-iphone-app

iPhone application and flash mob project, where the Format festival zine community in Adelaide can create and self-publish electronic zines and photo comics.
Objective-C
4
star
19

couchdbsyncer-android

android library for synchronising a couchdb database to a local sqlite database
Java
4
star
20

dootstrapper

🚀️ Development Tools Bootstrapper 🚀️
TypeScript
4
star
21

FazeKit

A collection of helper functions and extensions for Swift iOS apps
Swift
4
star
22

loopback-faker-mixin

A mixin for creating random data on loopback models using the faker library
JavaScript
4
star
23

iStatusView

Swift
3
star
24

PagedCollectionView

A centered paged collection view
Swift
3
star
25

NFCache

elite caching framework
Objective-C
3
star
26

loopback-decorators

Remote method decorators for loopback
TypeScript
3
star
27

soundcloud-player-webview

Objective C Sound cloud player using a UIWebView
C
2
star
28

ngx-translate-quickcreate

Quickly and easily turn hard coded strings into ngx-translate pipes
TypeScript
2
star
29

jpeg2moro

jpeg + transparency
C
2
star
30

CharacterEntityConverter

Objective C library for character entity conversions
Objective-C
2
star
31

cdk-extensions

The one-stop shop for most common CDK extensions
TypeScript
2
star
32

couchdbsyncer-android-example

Android: example application using the couchdbsyncer-android library
Java
2
star
33

KillSwitch-cocos2dx

Remote configuration and control for iOS and Android apps built with Cocos2D-X
C++
2
star
34

ldjam48

ShaderLab
2
star
35

diy-comic-webviewer

The diy-comic webviewer is a simple webapp to allow the comics submitted to Flickr throu the DIYComic App (http://github.com/2moro/diy-comic-iphone-app) to be viewed on the web in a similar fashion to the "View Entries" feature in the App.
PHP
2
star
36

NFRecord

implementation of an activerecord-like pattern for objective c
Objective-C
1
star
37

run-dino-run

A simple 2d infinite runnin game, built with flutter
Dart
1
star
38

NFURLConnection

basic framework for sending network requests and parsing responses
Objective-C
1
star
39

parse-extras-android

An Android library project containing login and signup activities for parse (parse.com) sdk similar in appearance to iOS versions
1
star
40

zxing-standalone-cipher

Android Studio project for compiling the ZXing barcode scanner example app, with support added for CipherLabs hardware.
Java
1
star
41

npm-modules

Useful modules and utilities for Angular, Loopback and the like
TypeScript
1
star
42

always-a-winner

An experiment in app reproduction
Objective-C
1
star
43

angular-airbrake

Airbrake error reporting service for Angular 2+
TypeScript
1
star
44

NFFileManager

Objective-C
1
star