Anthony Kiniyalocts - Big Nerd Ranch Wed, 16 Nov 2022 21:35:50 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Splash Screens: The Final Right Way https://bignerdranch.com/blog/splash-screens-the-final-right-way/ https://bignerdranch.com/blog/splash-screens-the-final-right-way/#respond Thu, 21 Oct 2021 20:47:02 +0000 https://bignerdranch.com/?p=9109 The impending release of Android 12 brings with it a group of new APIs for Android developers to learn and play with. Being a good citizen in the Android developer community means always giving the users of our applications a smooth transition when upgrading our targetSdk. In Android 12, there are quite a few changes affecting our users […]

The post Splash Screens: The Final Right Way appeared first on Big Nerd Ranch.

]]>
The impending release of Android 12 brings with it a group of new APIs for Android developers to learn and play with. Being a good citizen in the Android developer community means always giving the users of our applications a smooth transition when upgrading our targetSdk. In Android 12, there are quite a few changes affecting our users and in this blog post we will focus on the upcoming App Splash Screen changes.

Splash screens have quite a history with Android, and the Ranch. In a previous post here we detailed the “right way” to add a splash screen to your Android app by overriding android:windowBackground. Now, splash screen support has been grafted into the Android platform (and backported with an androidx library). Starting with Android 12, App Splash Screens are enabled by default, and if you do not take the time to update your application when targeting Android 12, it may result in an undesirable effect.

To illustrate these changes, I’ve updated an example app from my previous post on DiffUtil. I’ve modified my build.gradle to change my targetSdk and compileSdk to 31 and here are the results on my device running the Android 12 beta:

Not bad. By default on Android 12, the Splash Screen API uses the windowBackground of your theme if it’s a single color and the launcher icon. But what about the majority of our users, on devices below Android 12?

The same build on a pre-Android 12 device:

Well, that was anticlimactic… 😒

AndroidX to the Rescue!

Like most things in the Android world, we can access backports of newer APIs via an androidx dependency. To start, add the following line to your build.gradle:

implementation "androidx.core:core-splashscreen:1.0.0-alpha02"

Sync your project with gradle files, then we’ll make an adjustment to our applications theme.

Create a new style, with it’s parent set to Theme.SplashScreen, along with a few other attributes you can customize:

<style name="Theme.Splash" parent="Theme.SplashScreen">
    <item name="postSplashScreenTheme">@style/Theme.App</item>
    <item name="windowSplashScreenBackground">#FF27140F</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/app_icon</item>
</style>
  • postSplashScreenTheme is a reference to your applications “normal” theme that will be set after your splash screen has finished showing.
  • windowSplashScreenBackground sets the background color of your splash screen.
  • windowSplashScreenAnimatedIcon references the icon you wish to show in the center of your splash screen. You may have noticed the “Animated” part of windowSplashScreenAnimatedIcon 😉 we’ll cover that a bit later.

Next, modify your application theme in your AndroidManifest.xml:

<application
        --------
        android:theme="@style/Theme.Splash"
        -------
        >

Finally, in your launch Activitybefore setContentView() is called (order matters here) in your onCreate() method, add the following method call:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    installSplashScreen() // <---- this one!!

    setContentView(R.layout.activity_main)
}

That’s it! Let’s build and run our application on a pre-Android 12 device to see what has changed.

👌 nice

The androidx splash screen library also lets us customize the splash screen’s exit animation. Let’s add a quick fade exit animation:

installSplashScreen().setOnExitAnimationListener { splashScreenView ->

        val alpha = ObjectAnimator.ofFloat(
            splashScreenView.view,
            View.ALPHA,
            1f,
            0f
        )
        alpha.duration = 500L

        alpha.doOnEnd { splashScreenView.remove() }

        alpha.start()
    }
}

🎉 It’s always nice to bring new features to older versions of Android, as we know OS updates can take some time to propogate through our user base. But, this doesn’t mean we should not take advantage of new APIs available to us.

You may have read Google’s Splash Screen Guide and noticed a few more features available on Android 12. The Splash Screen platform API supports Animated Vector Drawables and even allows us to add a branding image to our splash screen.

Taking a look at the docs:

The app icon (1) should be a vector drawable, and it can be static or animated. Although animations can have an unlimited duration, we recommend that it not exceed 1,000 milliseconds. By default, the launcher icon is used.

Let’s take adavantage of this by animating a “ring” around our Planets application icon. I’ve converted the VectorDrawable icon to an AnimatedVectorDrawable and added this animation:

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
        ----
        [my vector icon]
        ---
        </vector>
    </aapt:attr>

    <target android:name="ring">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathEnd"
                android:startOffset="500"
                android:duration="500"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>

Let’s take a look…

Neat!

It’s nice to see Android have a standard for App Splash Screens built into the platform. Up until now, it was common practice to implement your own splash screen for your application. If you did implement a splash screen, it’s important to note any unintended side effects when upgrading your targetSdk to Android 12. Applications using a separate Activity for their splash screen without a properly configured theme may result in a user seeing 2 splash screens! 😳

Cheers to another Android release, and thanks for reading.

Happy coding!

P.S. 📣 Special thanks to Clint M. and Donovan L. for design help! 🙌

The post Splash Screens: The Final Right Way appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/splash-screens-the-final-right-way/feed/ 0
Efficient lists with DiffUtil and ListAdapter https://bignerdranch.com/blog/efficient-lists-with-diffutil-and-listadapter/ https://bignerdranch.com/blog/efficient-lists-with-diffutil-and-listadapter/#respond Thu, 03 Jun 2021 10:00:33 +0000 https://bignerdranch.com/?p=7510 Writing code to display lists of data is an everyday task as a software engineer, regardless of platform. Operations to modify lists can vary in complexity, and force developers down paths that sacrifice user experience or performance. This article will show you how to leverage DiffUtil and ListAdapter to avoid those pitfalls, and provide a more efficient user experience, […]

The post Efficient lists with DiffUtil and ListAdapter appeared first on Big Nerd Ranch.

]]>
Writing code to display lists of data is an everyday task as a software engineer, regardless of platform. Operations to modify lists can vary in complexity, and force developers down paths that sacrifice user experience or performance. This article will show you how to leverage DiffUtil and ListAdapter to avoid those pitfalls, and provide a more efficient user experience, while reducing boilerplate code.

On Android, RecyclerView is the go-to API for efficiently displaying lists of data. Our data is often changing, so out of the box, RecyclerView.Adapter comes with many low-level granular APIs to update our user interface as the backing data changes, such as:

notifyItemChanged(position: Int)
notifyItemRemoved(position: Int)
notifyItemRangeChanged(positionStart: Int, itemCount: Int)

and many more

In order to explore these APIs, I’ve created a small sample app listing the known planets of our solar system shown in descending order by their distance from the sun. It seems Pluto has snuck its’ way into our list, so let’s remove it with notifyItemRemoved():

adapter.items.remove(pluto)
adapter.notifyItemRemoved(plutoIndex)

Assuming you have access to the position of the item to be removed, we can make the appropriate method calls to remove the item from the list and provide smooth animations for our user to visualize the change.

These types of interactions are manageable for small changes but don’t scale well. For a more complex example, let’s have some of our planets change position. (Please ignore the real-world implications of this 🙈) Jupiter moves in front of Saturn, Mars and Earth swap, and Venus is flung out beyond Neptune. Resulting in an order like this:

[ Venus, Neptune, Uranus, Jupiter, Saturn, Earth, Mars, Mercury ]

How would we go about showing these changes to the user with the RecyclerView.Adapter APIs we have available? notifyItemMoved(fromPosition: Int, toPosition: Int) comes to mind… But that would involve keeping track of each items’ individual movement. These low-level APIs can be tedious to implement for complex use-cases, and are not always kept in mind when architecting your application. This tends to lead developers to use the “catch-all” notifyDataSetChanged() method when updating their adapters. From the official documentation, we learn this is not ideal for the best user experience:

This event does not specify what about the data set has changed, forcing any observers to assume that all existing items and structure may no longer be valid. LayoutManagers will be forced to fully rebind and relayout all visible views.

And even goes on to say:

If you are writing an adapter it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort.

Here is an example of notifyDataSetChanged() displaying our new list of rearranged planets:

adapter.items = rearrangedPlanets

adapter.notifyDataSetChanged()

Our data is displayed accurately, and the steps to make that change were much easier, but it resulted in a jarring user experience along with inefficient use of RecyclerView APIs.

Fortunately, there have been many additions that augment the ease-of-use of RecyclerView and RecyclerView.Adapter.

One in particular that has gained some much-needed attention since release is DiffUtil. From the official documentation:

DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.

Seems simple enough. I have my oldList, and my newList. What are the minimum amount of steps needed to migrate oldList => newList and display those steps to my user? –A perfect use-case to display the milky-way’s newly arranged solar system! 🪐

Create a new class, extending DiffUtil.Callback. We supply this class with our new and old list and it calculates a DiffResult.

areItemsTheSame() determines if oldItem and newItem are the same item (usually represented by comparing a unique id). areContentsTheSame() determines if the values of newItem and oldItem have changed. A result is calculated determining the item’s new position (if it was moved), and it’s new contents (if it has changed).

class PlanetDiffUtilCallback(private val oldList: List<Planet>, private val newList: List<Planet>): DiffUtil.Callback() {

    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

Add a method to our PlanetAdapter (a subclass of RecyclerView.Adapter):

fun update(newList: List<Planet>){
    val callback = PlanetDiffUtilCallback(items, newList)
    val result = DiffUtil.calculateDiff(callback)
    currentItems = newList // don't forget to update the backing list
    result.dispatchUpdatesTo(this)
}

From our Activity or Fragment:

planetBasicAdapter.update(rearrangedPlanets)

How wonderful! Each modified planet in our RecyclerView is updated with its new location, and the user is notified of the granular changes in the list with smooth animations 😎.

As perfect as DiffUtil seems, it does come with some limitations. From the documentation :

The actual runtime of the algorithm significantly depends on the number of changes in the list and the cost of your comparison methods… Due to implementation constraints, the max size of the list can be 2^26.

So DiffUtil could run into issues on very large lists with heavy modifications. It’s also worth noting, that in our current implementation, the “diffing” calculation is being done on the main thread. This could lead to unwanted UI “jank”. Fortunately, DiffUtil offers a way to move its calculation to a background thread.

To send DiffUtil calculations to the background, Android offers us AsyncListDiffer. For simplicity, the ListAdapter wrapper class can be used instead of AsyncListDiffer directly. Below is a modified version of our above example, using ListAdapter:

class PlanetListAdapter: ListAdapter<Planet, PlanetViewHolder>(PlanetDiff) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlanetViewHolder {
        return PlanetViewHolder(ItemPlanetBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    override fun onBindViewHolder(holder: PlanetViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

PlanetDiff object:

object PlanetDiff: DiffUtil.ItemCallback<Planet>(){
    override fun areContentsTheSame(oldItem: Planet, newItem: Planet): Boolean {
        return oldItem == newItem
    }

    override fun areItemsTheSame(oldItem: Planet, newItem: Planet): Boolean {
        return oldItem.id == newItem.id
    }
}

ListAdapter‘s implementation requires us to override onCreateViewHolder() and onBindViewHolder(), 1 less method than a normal RecyclerView.Adapter. For most use-cases, ListAdapter should be our go-to RecyclerView.Adapter class because of its reduced boilerplate and added functionality.

From our Activity or Fragment:

planetAdapter.submitList(rearrangedPlanets)

That’s it! Now the application is performing its diff calculation on a background thread, saving our user from any potential lag. We also wrote less code and reduced boilerplate using ListAdapter.

This simple example shows how ListAdapter and DiffUtil can be used to avoid hardship without sacrificing performance while displaying and modifying lists of data. APIs like this have been a great addition to the Android platform, so much so that other platforms have followed suit, implementing their own versions of DiffUtil, all based on the Myers difference algorithm.

The post Efficient lists with DiffUtil and ListAdapter appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/efficient-lists-with-diffutil-and-listadapter/feed/ 0