
Following a long tradition, along with the new version of Android, the Support Library is being updated. So far, the library has been released in the alpha stage, but the list of changes is already much more interesting than the same list for Android P. Google has told an unfair little and wrote about the main innovations of the main library for Android. We have to read the source and understand what the features of the new features are and why they are needed. I will restore justice and tell you how Google pleased us:
- RecyclerView selection - the selection of items is now out of the box;
- Slices - a new way to display the content of another application;
- new design elements: BottomAppBar, ChipGroup and others;
- minor changes in one line.
RecyclerView selection
In 2014, along with the release of Lollipop, Google added a new item to support - RecyclerView, as a replacement for the outdated ListView. Everything was fine with it, and there was a lack of one method from ListView - setSelectionMode (). After 4 years, this method was indirectly implemented in RecyclerView as a whole library.
')
What is magic in selection? Selection mode - mode, which is initialized by long pressing on the list item. Then we can select several other elements and make a general action on them. Example: Google Photos selection mode makes life easier.

Let's understand in practice how it is in support.
Add dependencies to the gradle. Interestingly, Google allocated selection to a separate repository.
dependencies { implementation "com.android.support:recyclerview-selection:28.0.0-alpha1" }
Let's write a standard adapter for RecyclerView.
class WordAdapter(private val items: List<Word>) : RecyclerView.Adapter<WordViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = WordViewHolder( LayoutInflater .from(parent.context) .inflate(R.layout.item_word, parent, false) ) override fun getItemCount() = items.size override fun onBindViewHolder(holder: WordViewHolder, position: Int) { val item = items[position] holder.bind(item) } class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val text: TextView = itemView.findViewById(R.id.item_word_text) fun changeText(word: Word) { text.text = word.text } } }
Model Word is used as data.
@Parcelize data class Word(val id: Int, val text: String) : Parcelable
The foundation is, let's proceed to the implementation of the choice. First you need to decide what will identify the list item. Google offers a choice of three options: Long, String, Parcelable. For this purpose, we have already formed Word, lacking only the implementation of Parcelable. The implementation will add the @ Parcelize annotation, which is available in the experimental version of Kotlin. In Android Studio 3.2, there are still problems with building a project with an experimental Kotlin, but no one has canceled studio templates.
SelectionTracker is the main class of the library. The object of this class has information about the elements selected by the user and allows changing this list from the code. To initialize this class, you will need the implementation of two abstract classes: ItemKeyProvider and ItemDetailsLookup. The first is needed for two-way communication of the position of the element in the collection and the key.
// ItemKeyProvider : // SCOPE_MAPPED - . , // SCOPE_CACHED - , . class WordKeyProvider(private val items: List<Word>) : ItemKeyProvider<Word>(ItemKeyProvider.SCOPE_CACHED) { override fun getKey(position: Int) = items.getOrNull(position) override fun getPosition(key: Word) = items.indexOf(key) }
ItemDetailsLookup is needed to get the position of an item and its key in x and y coordinates.
class WordLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<Word>() { override fun getItemDetails(e: MotionEvent) = recyclerView.findChildViewUnder(ex, ey) ?.let { (recyclerView.getChildViewHolder(it) as? ViewHolderWithDetails<Word>)?.getItemDetail() } }
We also write an interface for receiving data from the ViewHolder and implement it.
interface ViewHolderWithDetails<TItem> { fun getItemDetail(): ItemDetails<TItem> } class WordDetails(private val adapterPosition: Int, private val selectedKey: Word?) : ItemDetails<Word>() { override fun getSelectionKey() = selectedKey override fun getPosition() = adapterPosition } inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ViewHolderWithDetails<Word> { override fun getItemDetail() = WordDetails(adapterPosition, items.getOrNull(adapterPosition)) }
Everywhere standard code. It's amazing why the support library developers didn't add the classic implementation themselves.
Let's create a tracker in the Activity.
val tracker = SelectionTracker .Builder<Word>( // "someId", recyclerView, // Long ItemKeyProvider StableIdKeyProvider WordKeyProvider(items), WordLookup(recyclerView), // Long String StorageStrategy.createParcelableStorage(Word::class.java) ).build()
Let's correct the ViewHolder, add a reaction to a change in the selection state.
fun setActivatedState(isActivated: Boolean) { itemView.isActivated = isActivated }
Add the tracker to the adapter, redefine onBindViewHolder with payload. If the changes relate only to the selection status, then the SelectionTracker.SELECTION_CHANGED_MARKER constant will appear in the payloads.
override fun onBindViewHolder(holder: WordViewHolder, position: Int, payloads: List<Any>) { holder.setActivatedState(tracker.isSelected(items[position])) if (SelectionTracker.SELECTION_CHANGED_MARKER !in payloads) { holder.changeText(items[position]) } }
Tracker is ready and works like a clock. Add a little beauty and meaning. Let the AppBar change color, the title will display the number of selected items and the Clear button will be added to the menu when the user selects something. For this, there is an ActionMode and its support in AppCombatActivity.
First of all, let's write the implementation of ActionMode.Callback.
class ActionModeController( private val tracker: SelectionTracker<*> ) : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.action_menu, menu) return true } override fun onDestroyActionMode(mode: ActionMode) { tracker.clearSelection() } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = true override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean = when (item.itemId) { R.id.action_clear -> { mode.finish() true } else -> false } }
Add observer to SelectionTracker and link the changes in the tracker with the ActionMode in the Activity.
tracker.addObserver(object : SelectionTracker.SelectionObserver<Any>() { override fun onSelectionChanged() { super.onSelectionChanged() if (tracker.hasSelection() && actionMode == null) { actionMode = startSupportActionMode(ActionModeController(tracker)) setSelectedTitle(tracker.selection.size()) } else if (!tracker.hasSelection()) { actionMode?.finish() actionMode = null } else { setSelectedTitle(tracker.selection.size()) } } }) } private fun setSelectedTitle(selected: Int) { actionMode?.title = "Selected: $selected" }
Now for sure. We enjoy simplicity and beauty.

We have made the standard version. Let me briefly note that Builder has many methods for customizing the selection process. For example, using the withSelectionPredicate (predicate: SelectionPredicate) method, you can limit the number of selected items or deny the selection of specific items. Builder also provides methods for adding behavior that may conflict with selection in the traditional way of adding. For example, using withOnDragInitiatedListener (listener: OnDragInitiatedListener) you can configure Drag & Drop.
Slices
Slice turned out to be the strangest novelty. Google has devoted very little time to explaining to the community what kind of a wonder it is. There is only code and documentation for half the classes. Let's figure it out.
For the basis, I’ll take the code from here, because they’ve figured out how to bypass the Permission bugs in Android P DP1. I want to note that Slices is not a new support library. The feature appeared in the Android SDK 28, and in support, the habitat has been expanded to version 24 of the SDK. This can complete the story and continue it in a few years. While minSdkVersion can be a maximum of 19, let's talk in general about the idea of this technology and why it is needed at all.
Slices is a library that allows you to request from one application (client or host) a part or a static piece of another application (sender or provider). Very similar to the description of RemoteViews, which is often used for programming custom widgets and notifications.
Slice is data in a framework without design and interactivity, like HTML without CSS and Js. The design will adapt to the theme of the host application.
Sample slice .
The sender is the ContentProvider, which needs to implement a simple onBindSlice method (sliceUri: Uri): Slice and inside the method to form a Slice. Our provider will send the time and number of calls.
class SliceContentProvider : SliceProvider() { private var counter = 0 override fun onBindSlice(sliceUri: Uri): Slice { return when (sliceUri.path) { "/time" -> createTimeSlice(sliceUri) else -> throw IllegalArgumentException("Bad url") } } override fun onCreateSliceProvider(): Boolean { Toast.makeText(context, "Slice content provider is launched", Toast.LENGTH_LONG).show() return true } private fun createTimeSlice(sliceUri: Uri): Slice = ListBuilder(context, sliceUri) .apply { counter++ setHeader( ListBuilder.HeaderBuilder(this) .setTitle("What's the time now?") ) addRow( ListBuilder.RowBuilder(this) .setTitle("It is ${SimpleDateFormat("HH:mm").format(Calendar.getInstance().time)}") ) addRow( ListBuilder.RowBuilder(this) .setTitle("Slice has called $counter times") ) } .build() }
The client needs to make a request for the URI to the provider, request slice through it, receive it and send it to SliceView. All actions are performed via SliceManager. It is important not to forget about permission.
private val baseSliceUri: Uri = Uri.parse("content://ru.touchin.provider/") private val timeSliceUri = baseSliceUri.buildUpon().appendPath("time").build() private lateinit var sliceManager: SliceManager override fun onCreate(savedInstanceState: Bundle?) {
SliceManager provides the ability to subscribe to Slice changes in the provider using SliceLiveData and update the SliceView within the subscription. Unfortunately, it is not working now. We used the less reactive version.
We start the provider, we start the application. Observe the result of the work. Everything cool. It's funny that the counter is incremented twice.

In most cases, RemoteView is used for widgets and notifications. Slices are poorly suited for these purposes, they are not very customizable and, as I already wrote, adapt to the design of the application. Ideal for applications that use data from other applications. Under the category of comprehensive fit voice assistants - Google Assistant, Alice, and so on. As noted in the Novada blog, using the slice constructor, you can collect slices that are very similar to the answers for Google Assistant.

And then it's time for a theory.
Let's take as a basis what Slice was made for programming answers in Google Assistant - a strategically important product for the company. Obviously, we live in a time when the graphical interface is gradually replaced by voice: the popularity of home assistants is growing and there is progress in the development of voice artificial intelligence through AI, neural networks and other hyip technologies.
For Google, the most logical option would be to develop and increase Google Assistant so that in a year or two it would become a powerful tool. Slice is theoretically a great tool for pumping add-ons from third-party developers. So the assistant will become more powerful, all actions can be carried out through it and the need for desktops and icons will disappear. Then Google Assistant will be the basis for Android.
At the moment, we didn’t really tell us anything about Slice: neither goals nor advantages over RemoteView. Although the number of code in the new version of support Slice takes almost the first place. So I think on the next I / O they will tell us in detail about Slice. And maybe talk about plans for the evolution of the OS, or even present the Android version with a voice interface for developers.
But all this is speculation and the desire of the author to reveal the conspiracy theory and get to the truth. The only thing that can be said for one hundred percent, on Google I / O is waiting for us the outcome of the story.
New items:
MaterialCardView and MaterialButton
MaterialCardView is inherited from CardView and is practically no different from it. Only the ability to set the borders of the card is added and another drawable is used as the background. Find 10 differences.
MaterialButton is the successor to AppCombatButton and the differences are noticeable here. The developers have added more ways to customize the button: the color of the ripple effect, the different radii of the buttons, the borders, like in MaterialCardView.

Chip and ChipGroup
Here and the words are superfluous.

Bottomomappbar
The most interesting and unexpected widget in this collection, although the idea is very simple, AppBar to place below. It is inconvenient for a user with small hands and large screens to reach the menu button or just the buttons on the AppBar at the top. But there is no other benefit in this element.
The menu on BottomAppBar needs to be added artificially, for this is the replaceMenu method (@MenuRes int newMenu).
Designers coolly figured out how to combine FloatingActionButton and BottomAppBar. But without the BottomAppBar button, it looks superfluous. The cut is removed, the chin remains with the menu buttons on one side. The problem with the menu on large screens could be solved more interestingly, for example, by long pressing the FloatingActionButton to transform it into the menu at the bottom of the screen.

List of short innovations:
- Android KTX, which was announced earlier. A bunch of open-source extensions on Kotlin. Very useful.
- HEIF Writer. A new format for encoding one or a sequence of images reached Android one year after the announcement on ios. This is not about a complete replacement of formats, like Apple. Just a library with conversion.
- Browser Actions is a protocol for customizing the browser context menu for a specific url. Customization is limited to adding several MenuItem with its own icon, text, and Intent. The protocol implies the implementation of logic by the browser as well. Chrome is not yet implemented.
For those who want to dig out:
- Use Android studio 3.1 and above. These versions are not yet in release, but they work stably, I worked with 3.2.
- A little mess in build.gradle with versions. Well, of course, you need to add the necessary dependencies.
android { compileSdkVersion 'android-P' defaultConfig { targetSdkVersion 'P' // 28 } }
- So far, the code that used support 28 was run only on an emulator with Android P. Everything that was older cursed and gave a lot of errors when trying to start.
The list of new features is not final. If we analyze the library’s changelog for the previous 2-3 years and extrapolate the data for this year, then in May we will have many, many more interesting things to expect. We wait.
Useful links: