Android Support Library 28. What's New?
According to a long tradition, the Support Library update is being released along with the new version of Android. So far, the library has reached the alpha stage, but the list of changes is already much more interesting than the same list for Android P. Google has unfairly told and wrote about the main innovations of the main library for Android. You have to read the source code and figure out what are the features of the new features and why they are needed. I’ll restore justice and tell you what Google pleased us with:
- RecyclerView selection - the selection of elements 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 element to support - RecyclerView, as a replacement for the obsolete ListView. Everything was fine with him, but one method from ListView was missing - setSelectionMode (). After 4 years, this method was indirectly implemented in RecyclerView as an entire library.
What is so magical about selection? Selection mode - a mode that is initialized by a long press on the list item. Next, we can select several other elements and make a general action on them. Example: Google Photos selection mode makes life much easier.
Let's see in practice how support works.
Add dependencies to gradle. Interestingly, Google pushed selection into 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) : RecyclerView.Adapter() {
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
}
}
}
We use the Word model as data.
@Parcelize
data class Word(val id: Int, val text: String) : Parcelable
There is a foundation; let us proceed with 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 generated Word, only the Parcelable implementation is missing. We’ll add the implementation with the @Parcelize annotation, which is available in the experimental version of Kotlin. In Android Studio 3.2, there are still problems with building the project with the experimental Kotlin, but no one has canceled the studio templates.
SelectionTracker is the main class of the library. An object of this class has information about user-selected elements and allows you to change this list from code. To initialize this class, you will need implementations 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) : ItemKeyProvider(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 the item and its key in x and y coordinates.
class WordLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup() {
override fun getItemDetails(e: MotionEvent) = recyclerView.findChildViewUnder(e.x, e.y)
?.let {
(recyclerView.getChildViewHolder(it) as? ViewHolderWithDetails)?.getItemDetail()
}
}
We will also write an interface for receiving data from ViewHolder and implement it.
interface ViewHolderWithDetails {
fun getItemDetail(): ItemDetails
}
class WordDetails(private val adapterPosition: Int, private val selectedKey: Word?) : ItemDetails() {
override fun getSelectionKey() = selectedKey
override fun getPosition() = adapterPosition
}
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ViewHolderWithDetails {
override fun getItemDetail() = WordDetails(adapterPosition, items.getOrNull(adapterPosition))
}
Everywhere the standard code. It's amazing why the developers of the support library did not add the classic implementation themselves.
We will form a tracker in Activity.
val tracker = SelectionTracker
.Builder(
// идентифицируем трекер в контексте
"someId",
recyclerView,
// для Long ItemKeyProvider реализован в виде StableIdKeyProvider
WordKeyProvider(items),
WordLookup(recyclerView),
// существуют аналогичные реализации для Long и String
StorageStrategy.createParcelableStorage(Word::class.java)
).build()
Correct ViewHolder, add a reaction to the change in the selection state.
fun setActivatedState(isActivated: Boolean) {
itemView.isActivated = isActivated
}
Add a tracker to the adapter, redefine onBindViewHolder with payload. If the changes concern only the selection state, then the payloads will contain the constant SelectionTracker.SELECTION_CHANGED_MARKER.
override fun onBindViewHolder(holder: WordViewHolder, position: Int, payloads: List) {
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 heading will begin to 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, we will 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 an observer to the SelectionTracker and associate the changes in the tracker with the ActionMode in the Activity.
tracker.addObserver(object : SelectionTracker.SelectionObserver() {
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. Enjoy the simplicity and beauty.
We made a standard version. Briefly, 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 prevent 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
The strangest novelty was Slice. Google devoted very little time to explaining to the community what kind of curiosity it is. There is only code and documentation for half the classes. Let's get it right.
I’ll take the code from here, because they figured out how to get around bugs with Permission 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 the 24th version 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 - 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 to program 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 a ContentProvider that needs to implement the simple onBindSlice (sliceUri: Uri): Slice method and form Slice inside the method. 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 by URI to the provider, request slice through it, receive and transfer it to SliceView. All actions are performed through 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?) {
// стандартные процедуры инициализации View
sliceManager = SliceManager.getInstance(this)
findViewById(R.id.get_slice).setOnClickListener {
tryShowingSlice(timeSliceUri)
}
}
override fun onStart() {
super.onStart()
if (providerAppNotInstalled(packageManager, baseSliceUri.authority)) {
showMissingProviderDialog(this, { finish() }, baseSliceUri)
return
}
}
private fun tryShowingSlice(sliceUri: Uri) {
if (sliceManager.missingPermission(sliceUri, appName = getString(R.string.app_name))) {
// запрашиваем permission сложным образом из-за Android P DP1
}
} else {
getSliceAndBind(sliceUri)
}
}
private fun getSliceAndBind(sliceUri: Uri) {
sliceView.setSlice(sliceManager.bindSlice(sliceUri))
}
SliceManager provides the ability to subscribe using SliceLiveData to Slice changes in the provider and update the SliceView inside the subscription. Unfortunately, it doesn’t work right now. We used a less reactive version.
We start the provider, we launch the application. We observe the result of work. Everything cool. It is 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 slightly customizable and, as I already wrote, are adapted to the design of the application. Ideal for applications that use data from other applications. Under the category of comprehensive voice assistants fit - Google Assistant, Alice, and so on. As noted on the Novada blog, using the slice constructor, you can collect slices that are very similar to the answers for Google Assistant.
And here is the time for theory.
Let's take as a basis the fact that Slice is made for programming answers in the Google Assistant - a strategically important product for the company. Obviously, we live in a time when the graphical interface is gradually being supplanted by the voice: home assistants are growing in popularity and there is progress in the development of voice artificial intelligence through AI, neural networks and other hype technologies.
For Google, the most logical option would be to develop and expand the Google Assistant, so that in a year or two it will become a powerful tool. Slice is a theoretically 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 disappears. Then the Google Assistant will become the basis for Android.
At the moment, they didn’t 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. Therefore, I think at the next I / O we will be told in detail about Slice. And perhaps they will talk about plans for the evolution of the OS or even introduce a version of Android with a voice interface for developers.
But all this is speculation and the author’s desire to uncover a conspiracy theory and get to the truth. The only thing that can be said one hundred percent, on Google I / O, the denouement of history awaits us.
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 background. Find 10 differences.
MaterialButton is the successor to AppCombatButton and here the differences are noticeable. The developers have added more ways to customize the button: the color of the ripple effect, different button radii, borders, like MaterialCardView.
Chip and ChipGroup
Here are superfluous words.
Bottomappppbar
The most interesting and unexpected widget in this collection, although the idea is very simple, place the AppBar below. It is inconvenient for a user with small hands and large screens to reach the menu button or just the button on the AppBar at the top. But there is no other benefit to this element.
The menu on the BottomAppBar needs to be added artificially, for this there is a replaceMenu method (@MenuRes int newMenu).
Designers coolly figured out how to combine FloatingActionButton and BottomAppBar. But without a button, the BottomAppBar looks superfluous. The cutout is removed, the chin with the menu buttons on one side remains. The problem with the menu on large screens could be solved more interesting, for example, by a long press on the FloatingActionButton to transform it into a 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. The new encoding format for one or a sequence of images reached Android a year after the announcement on ios. Here we are not talking about a complete replacement of formats, like Apple. Just a library with conversion.
- Browser Actions - a protocol for customizing the browser context menu for a specific url. Customization is limited to adding several MenuItems with their own icon, text, and Intent. The protocol implies the implementation of logic also from the side of the browser. Not yet implemented in Chrome.
For those who want to dig deeper:
- Use Android studio 3.1 and higher. These versions are not yet in release, but they work stably, I worked with 3.2.
- A little shaman 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 only ran on an emulator with Android P. Everything that was older cursed and gave a bunch of errors when trying to run.
The list of new features is not final. If we analyze the changelog libraries for the previous 2-3 years and extrapolate the data for this year, then in May we will expect many, many more interesting things. We wait.
Useful links:
- All code from the article .
- Good article about BottomAppBar . From here I took the code for a demonstration.
- A detailed story about Slices is again not from Google . I took some ideas and the basis for the code from here.
- A detailed analysis of useful little things from Android KTX