Smart Adapters


Source link: https://github.com/mrmans0n/smart-adapters

Smart Adapters Library

Android library project that intends to simplify the usage of Adapters for ListView/GridView and RecyclerView. You won't have to code any boring adapter again!

It helps to keep boilerplate to a minimum and adds the possibility of easily changing between BaseAdapter / RecyclerView.Adapter adapter styles without changing any code. It also allows painless usage of multiple models / view types for the same list or grid - just add the different mappings between model objects and view objects.

Formerly part of nl-toolkit.

Adding to your project

Add this to your dependencies:

compile 'io.nlopez.smartadapters:library:1.3.1'

Usage

Adapter creation

If we got the typical list with one object mapped to one view, for example Tweet -> TweetView, it's as simple as this:

SmartAdapter.items(myObjectList).map(Tweet.class, TweetView.class).into(myListView);

Note that we have to prepare a bit the view (TweetView in this case). Please read the "Preparing your view classes" section.

If we need to do a more complex list, with different models mapped to different views, we just have to add more map calls. Here is an example:

SmartAdapter.items(myObjectList)
  .map(Tweet.class, TweetView.class)
  .map(String.class, ListHeaderView.class)
  .map(User.class, UserView.class)
  .into(myListView);

You can pass an AbsListView based control (ListView, GridView) or a RecyclerView to the into methods. The class will use the appropriate adapter class depending on which control you pass in there.

We can change the items(...) call for empty() if we want an adapter initialized with an empty array.

The calls from before return the adapter, so if you want to store it for manipulating it afterwards you can do it. For example:

MultiAdapter adapter = SmartAdapter.empty().map(Tweet.class, TweetView.class).into(myListView);
  // We can add more stuff. The list will update and refresh its views. adapter.addItems(moreItems);
  // And delete it if we want! adapter.clearItems();

Preparing your view classes

All your view classes must implement the BindableLayout interface so we got a common entrypoint for binding the model data to the view.

If looks like a lot of work, but you have some already implemented base classes depending on which you want to use as your base View. You can take a look at some implementation examples on the sample project.

  • BindableFrameLayout
  • BindableLinearLayout (you have to implement getOrientation() here)
  • BindableRelativeLayout
  • BindableViewLayout for the adventurous not relying on ViewGroup
public class TweetView extends BindableFrameLayout<Tweet> {

// [...]

public TweetView(Context context) {

// This is the constructor that should be implemented, because it's the one used internally

// by the default builder.

super(context);

  
}

@Override
  public int getLayoutId() {

// This is mandatory, and should return the id for the view layout of this view

return R.layout.view_tweet;
  
}

@Override
  public void onViewInflated() {

// Here we should assign the views or use ButterKnife

ButterKnife.inject(this);

// Fixes some horizontal fill layout

setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

  
}

@Override
  public void bind(Tweet tweet) {

// In here we assign the model information values to our view widgets

 // Examples:

tweetText.setText(tweet.getText());

authorText.setText(tweet.getAuthor());

 // and so on!
  
}
 
}

I use an Android Studio template for creating the BindableLayouts. You can find it here.

A nice side effect is that we can pretty much switch back and forth to using ListView or RecyclerView without having to change anything in these views. We also got some more granular control over the events for those view widgets. We could pretty much add different onClick events to the row, to some button inside of it, etc.

We can notify the listener with some specific calls to be able to handle the events wherever the adapter is being instantiated, instead of doing it all inside the view code (which would be pretty messy).

 public void bind(MyModel model) {

// Set a global click handler

setOnClickListener(new OnClickListener() {

 @Override

 public void onClick(View v) {

  notifyItemAction(ROW_PRESSED);

 
}

}
);

favoriteButton.setOnClickListener(new OnClickListener() {

 @Override

 public void onClick(View v) {

  notifyItemAction(FAVORITE_PRESSED);

 
}

}
);

  
}

Assigning listeners

If we want to control any event in our view classes at the adapter level, we can do it. We just have to add a listener to the adapter, and it could be done when instantaiting it or after.

For example:

SmartAdapter.items(myObjectList)
  .map(Tweet.class, TweetView.class)
  .listener(myViewListener)
  .into(myListView);

The listener would be like this:

myViewListener = new ViewEventListener<Tweet>() {

  @Override
  public void onViewEvent(int actionId, Tweet item, View view) {

// actionId would be any constant value you used in notifyItemAction.
  
}
 
}
;

Advanced usage with custom builders

If we want to display different cells depending on the data of a single model or something more convoluted, we can specify our own BindableLayoutBuilder interface for the classes to be instantiated. The library allows multiple views mapped to the same view object as long as you provide a specific implementation for the viewType method in a BindableLayoutBuilder.

Here we have an example of a custom BindableLayoutBuilder created for a hypothetical case where the view class depends on the values of the object. Ideally it's enough to just overwrite the viewType method and return values depending on the desired view class. Also, allowing multiple views for the same type of object has to return true in the allowsMultimapping method.

public class TweetBindableLayoutBuilder extends DefaultBindableLayoutBuilder {

@Override
  public Class<? extends BindableLayout> viewType(@NonNull Object item, int position, @NonNull Mapper mapper) {

if (item instanceof Tweet) {

 // All the multiple bindings must be dealt with here and NOT get into the fallback

 Tweet tweet = (Tweet) item;

 if (tweet.hasGallery()) {

  return TweetGalleryView.class;

 
}
 else if (tweet.hasImage()) {

  return TweetImageView.class;

 
}
 else if (tweet.hasEmbeds()) {

  return TweetEmbedView.class;

 
}
 else {

  return TweetView.class;

 
}

}

// With this fallback we return control for all the other cases to be handled as the default use.

return super.viewType(item, position, mapper);

  
}

@Override
  public boolean allowsMultimapping() {

return true;
  
}
 
}
 

We can add it to the adapter like this:

SmartAdapter.items(myObjectList)
  .listener(myViewListener)
  .map(Tweet.class, TweetView.class)
  .map(Tweet.class, TweetGalleryView.class)
  .map(Tweet.class, TweetImageView.class)
  .map(Tweet.class, TweetEmbedView.class)
  .map(OtherThing.class, OtherThingView.class)
  .builder(new TweetBindableLayoutBuilder())
  .into(myListView);

You have a working example of this particular case in the samples.

You can also check more examples on how to implement builders in the default builders included with the library.

The idea behind the builders is that you can, given the object and its class, create the view class by yourself and return to the adapter.

Common issues

If you are already using RecyclerView in your project and have problems compiling, you can try setting the transitive property to false:

compile ('io.nlopez.smartadapters:library:1.3.1') {

  transitive = false 
}

If your row doesn't fill horizontally your RecyclerView, you should specify the LayoutParams to MATCH_PARENT.

setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

Please be aware that the type of the LayoutParams might have to be changed depending on where you are inflating the view. If you got ClassCastException on this, you know you have to change the type of the LayoutParms here.

Contributing

Forks, patches and other feedback are welcome.

Creators

Nacho López - Github @mrmans0n - Twitter @mrmans0n - Blog nlopez.io

License

MIT License

Resources

Type safe bundle/intent building for any class (activities, fragments, ...)

Add badge (version code, version name, etc) for your DEBUG APK.

This library will help you to pass and receive fragment arguments in easier way.

Kotlin Android Library, that makes preference usage simple and fun.

An example app to take screenshot/images of a particular view.

Zoomy is an easy to use pinch-to-zoom Android library.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes