[MUSIC PLAYING]
MURAT YENER: If you ever use the java.time API in your app,
you may have ended up with lots of crashes and complaints
from users with devices running below API level 26.
The reason is that the devices which encountered the issues
simply didn't ship with the necessary classes
as, at the time, those classes were not part of the Android.
I'm Murat Yener, a developer advocate for Android,
and I'll be talking about Java language updates on Android.
With each new release of Android,
additional Java APIs are added from OpenJDK to Android.
In Android 11, we added support for a number of APIs
from newer OpenJDK releases, all the way up to version 13,
including additions to List, Set, Map, and the new java.time
API.
While we continuously add new Java APIs to each platform
release, we also want to make sure these APIs are
available for older devices.
What if I told you that when you use Android Gradle plugin
4.0 or newer, you can now use several hundred APIs from newer
OpenJDK versions, and your app will
work on any Android device?
Some of these newer Java APIs in Android 11
are supported through backporting,
while other APIs are also available via desugaring
on older devices where the Android platform does not
have the APIs on the runtime.
If you are wondering how that's even possible,
let's take a look at desugaring.
The Android Gradle plugin 4.0 provides built-in support
for using certain Java language APIs and third parties that
use them.
Previously, these APIs were only supported
on newer Android versions.
But with AGP 4.0, they are now supported on nearly all Android
versions.
This support is possible with a trick called desugaring,
which is performed by D8 and R8.
When you build an app using these new Java APIs,
the Java compiler first converts Java source code
into Java bytecode.
Then the tool chain implements new APIs
by performing bytecode transformations on your app,
including any third-party libraries
your app uses, and converts them into text code.
Finally, the necessary newer Java runtime
is added as a separate text library
for devices that are missing those runtime classes.
This process is called desugaring,
and it enables a set of Java APIs
to work on all existing devices, except parallel, which
is supported from API level 21.
To start using the newer Java APIs, update the Android Gradle
plugin to 4.0 or higher.
And, in your module's build.gradle file,
set coreLibraryDesugaringEnabled flag.
Set the Java source code targetCompatibility to Java 8.
Add coreLibraryDesugaring as a dependency.
And enable multiDex since, to support these newer APIs,
the plugin compiles a separate text file
that contains an implementation of the missing APIs
and includes it in your app.
Now that you've learned about and enabled desugaring,
let's take a look at the new APIs.
The old Java util time API had some drawbacks.
java.util.Date and java.util.Calendar classes
are mutable, which can cause concurrency issues.
Also, the API is not very consistent and easy to use.
To give a simple example, in a date object,
date starts from 1, but month starts
from 0, which can be confusing.
With OpenJDK version 8, a new Java time API,
java.time package, is added to Java.
The new java.time is based on the popular library, Joda-Time.
All core java.time classes are immutable
and don't have [INAUDIBLE] methods,
so they don't introduce any concurrency issues.
Plus, the new java.time API makes working
with time zones much easier.
Let's take a look at the new date and time classes.
First, if you don't need time zone data on date and time
objects, java.time has two new classes--
LocalDate and LocalTime.
These two classes represent date and time
relative to the user, such as an alarm clock or a timer,
without worrying about time zones.
If your use case requires time zones,
you can use ZonedDateTime or OffsetDateTime classes.
Let's start with ZonedDateTime.
ZonedDateTime is an immutable representation of date time
that holds a local date time, a zone ID, and the result
zone offset.
Time zones can be set either abbreviated or long text form
of the zone using ZoneId class.
ZonedDateTime is particularly useful
when you need to store date and time
without relying on the context of a specific device or an app.
ZonedDateTime can be resolved to any time zone
at any point in time.
OffsetDateTime is similar to ZonedDateTime,
but keeps the offsets from Greenwich UTC instead
of ZoneId.
You can use zone offsets to calculate
the differences between the current time zone and Greenwich
UTC.
To summarize the differences between these two date time
classes, ZonedDateTime is great for displaying
time zone-sensitive data in your app.
OffsetDateTime is best for storing date time to a database
or other use cases where the data needs to be serialized.
The new java.time API has two new classes--
period and duration-- to define a date range or length of time,
respectively.
Next, we have streams.
Streams allow you to perform functional style operations
on collections.
Streams do not store or modify the underlying data structure
and offer much better readability.
Streams have a large number of built-in intermediate and
terminal operations.
Intermediate operations are lazy and always return a new stream.
Terminal operations are eager and, once invoked,
they complete the traversal of the data source,
and the stream can no longer be used.
Since intermediate operations are not
evaluated until terminal operation is invoked,
streams may perform better when working on large data sources.
To use streams, you can call the stream method on a collection,
or use Stream.of and pass your data source,
or use the stream builder.
There are new additions to map, collection, and comparator
interfaces under java.util. package.
Writing a comparator is not hard,
but requires a lot of boilerplate code
where you can easily make mistakes.
In Java 8, you can use a simple chaining-style code
to write comparators.
With the help of method handles and lambdas,
a comparator in Java 8 looks as simple as this.
Another interesting addition to the java.util package is
the optional class and its primitive counterparts--
OptionalInt, OptionalLong, and OptionalDouble.
Optional helps you work with real types,
instead of null references.
Optional can represent null with the absence of the value.
You can use the utility methods to handle
values as available or not available,
instead of checking null values.
The optional class has a variety of utility methods,
such as orElse, orElseThrow, Filter, and more,
which help you handle null checks and null cases easily.
There are also new methods on AtomicInteger, AtomicLong,
and AtomicReference in the java.util.concurrrent.atomic
package.
Another update in java.util.concurrent packages,
bug fixes to the ConcurrentHashMap,
a thread-safe alternative to hash map.
ConcurrentHashMap has always been part of Android.
However, on API levels 21 and 22 the implementation had a bug.
A new, fixed implementation is part of the desugared library.
With library desugaring, date implementation
will be used in your app in place of the one
in the platform, which might have a bug.
If you are not familiar with ConcurrentHashMap,
this class allows any number of threads
to perform get operations.
For update and insert operations,
the performing thread must lock the particular segment
where the data is modified or inserted.
Although any thread can perform get operations,
this lock may block get operations
that try to access the data in the locked segment.
Due to synchronization, ConcurrentHashMap
can perform slower than a hash map,
in return providing thread safety.
Upgrade your projects to make full use of all newer
Java language APIs.
Streams, optionals, and the new time APIs
help you write less code and introduce fewer errors,
while using modern language APIs.
Make sure to take a look at the full list of supported newer
Java APIs, which is linked in the notes below.
Thanks for watching.
Go write beautiful Android apps by using the newer Java APIs.
And I'll see you next time.
[MUSIC PLAYING]