Tag Archives: Jetpack

MAD Skills Kotlin and Jetpack: wrap-up

Posted by Florina Muntenescu, Developer Relations Engineer

Kotlin and Jetpack image

We just wrapped up another series of MAD Skills videos and articles - this time on Kotlin and Jetpack. We covered different ways in which we made Android code more expressive and concise, safer, and easy to run asynchronous code with Kotlin.

Check out the episodes below to level up your Kotlin and Jetpack knowledge! Each episode covers a specific set of APIs, talking both about how to use the APIs but also showing how APIs work under the hood. All the episodes have accompanying blog posts and most of them link to either a sample or a codelab to make it easier to follow and dig deeper into the content. We also had a live Q&A featuring Jetpack and Kotlin engineers.

Episode 1 - Using KTX libraries

In this episode we looked at how you can make your Android and Jetpack coding easy, pleasant and Kotlin-idiomatic with Jetpack KTX extensions. Currently, more than 20 libraries have a KTX version. This episode covers some of the most important ones: core-ktx that provides idiomatic Kotlin functionality for APIs coming from the Android platform, plus a few Jetpack KTX libraries that allow us to have a better user experience when working with APIs like LiveData and ViewModel.

Check out the video or the article:

Episode 2 - Simplifying APIs with coroutines and Flow

Episode 2, covers how to simplify APIs using coroutines and Flow as well as how to build your own adapter using suspendCancellableCoroutine and callbackFlow APIs. To get hands-on with this topic, check out the Building a Kotlin extensions library codelab.

Watch the video or read the article:

Episode 3 - Using and testing Room Kotlin APIs

This episode opens the door to Room, peeking in to see how to create Room tables and databases in Kotlin and how to implement one-shot suspend operations like insert, and observable queries using Flow. When using coroutines and Flow, Room moves all the database operations onto the background thread for you. Check out the video or blog post to find out how to implement and test Room queries. For more hands-on work - check out the Room with a view codelab.

Episode 4 - Using WorkManager Kotlin APIs

Episode 4 makes your job easier with WorkManager, for scheduling asynchronous tasks for immediate or deferred execution that are expected to run even if the app is closed or the device restarts. In this episode we go over the basics of WorkManager and look a bit more in depth at the Kotlin APIs, like CoroutineWorker.

Find the video here and the article here, but nothing compares to practical experience so go through the WorkManager codelab.

Episode 5 - Community tip

Episode 5 is by Magda Miu - a Google Developer Expert on Android who shared her experience of leveraging foundational Kotlin APIs with CameraX. Check it out here:

Episode 6 - Live Q&A

In the final episode we launched into a live Q&A, hosted by Chet Haase, with guests Yigit Boyar - Architecture Components tech lead, David Winer - Kotlin product manager, and developer relations engineers Manuel Vivo and myself. We answered questions from you on YouTube, Twitter and elsewhere.

Prefer Storing Data with Jetpack DataStore

Posted by Florina Muntenescu, Android Developer Advocate, Rohit Sathyanarayana, Software Engineer

Welcome Jetpack DataStore, now in alpha - a new and improved data storage solution aimed at replacing SharedPreferences. Built on Kotlin coroutines and Flow, DataStore provides two different implementations: Proto DataStore, that lets you store typed objects (backed by protocol buffers) and Preferences DataStore, that stores key-value pairs. Data is stored asynchronously, consistently, and transactionally, overcoming most of the drawbacks of SharedPreferences.

SharedPreferences vs DataStore

SharedPreferences

* SharedPreferences has a synchronous API that can appear safe to call on the UI thread, but which actually does disk I/O operations. Furthermore, apply() blocks the UI thread on fsync(). Pending fsync() calls are triggered every time any service starts or stops, and every time an activity starts or stops anywhere in your application. The UI thread is blocked on pending fsync() calls scheduled by apply(), often becoming a source of ANRs.

** SharedPreferences throws parsing errors as runtime exceptions.

In both implementations, DataStore saves the preferences in a file and performs all data operations on Dispatchers.IO unless specified otherwise.

While both Preferences DataStore and Proto DataStore allow saving data, they do this in different ways:

  • Preference DataStore, like SharedPreferences, has no way to define a schema or to ensure that keys are accessed with the correct type.
  • Proto DataStore lets you define a schema using Protocol buffers. Using Protobufs allows persisting strongly typed data. They are faster, smaller, simpler, and less ambiguous than XML and other similar data formats. While Proto DataStore requires you to learn a new serialization mechanism, we believe that the strongly typed schema advantage brought by Proto DataStore is worth it.

Room vs DataStore

If you have a need for partial updates, referential integrity, or support for large/complex datasets, you should consider using Room instead of DataStore. DataStore is ideal for small , simple datasets and does not support partial updates or referential integrity.

Using DataStore

Start by adding the DataStore dependency. If you’re using Proto DataStore, make sure you also add the proto dependency:

// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"


// Proto DataStore
implementation  "androidx.datastore:datastore-core:1.0.0-alpha01"

When working with Proto DataStore, you define your schema in a proto file in the app/src/main/proto/ directory. See the protobuf language guide for more information on defining a proto schema.

syntax = "proto3";

option java_package = "<your package name here>";
option java_multiple_files = true;

message Settings {
  int my_counter = 1;
}

Create the DataStore

Create the DataStore with the Context.createDataStore() extension functions.

// with Preferences DataStore
val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings"
)

If you’re using Proto DataStore, you’ll also have to implement the Serializer interface to tell DataStore how to read and write your data type.

object SettingsSerializer : Serializer<Settings> {
    override fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
}


// with Proto DataStore
val settingsDataStore: DataStore<Settings> = context.createDataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)

Read data from DataStore

DataStore exposes the stored data in a Flow, either in a Preferences object or as the object defined in your proto schema. DataStore ensures that data is retrieved on Dispatchers.IO so your UI thread isn’t blocked.

With Preferences DataStore:

val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
     .map { currentPreferences ->
        // Unlike Proto DataStore, there's no type safety here.
        currentPreferences[MY_COUNTER] ?: 0   
   }

With Proto DataStore:

val myCounterFlow: Flow<Int> = settingsDataStore.data
    .map { settings ->
        // The myCounter property is generated for you from your proto schema!
        settings.myCounter 
    }

Write data to DataStore

To write data, DataStore offers a suspending DataStore.updateData() function that gives you the current state of the stored data as a parameter—either as a Preferences object, or an instance of the object defined in the proto schema. The updateData() function updates the data transactionally in an atomic read-write-modify operation. The coroutine completes once the data is persisted on disk.

Preferences DataStore also provides a DataStore.edit() function to make it easier to update data. Instead of receiving a Preferences object, you receive a MutablePreferences object which you edit. As with updateData(), the changes are applied to disk after the transform block completes, and the coroutine completes once data is persisted to disk.

With Preferences DataStore:

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        // We can safely increment our counter without losing data due to races!
        val currentCounterValue = settings[MY_COUNTER] ?: 0
        settings[MY_COUNTER] = currentCounterValue + 1
    }
}

With Proto DataStore:

suspend fun incrementCounter() {
    settingsDataStore.updateData { currentSettings ->
        // We can safely increment our counter without losing data due to races!
        currentSettings.toBuilder()
            .setMyCounter(currentSettings.myCounter + 1)
            .build()
    }
}

Migrate from SharedPreferences to DataStore

To migrate from SharedPreferences to DataStore, you need to pass in a SharedPreferencesMigration object to the DataStore builder. DataStore can automatically migrate from SharedPreferences to DataStore for you. Migrations are run before any data access can occur in DataStore. This means that your migration must have succeeded before DataStore.data returns any values and before DataStore.updateData() can update the data.

If you’re migrating to Preferences DataStore, you can use the default SharedPreferencesMigration implementation and just pass in the name used to construct your SharedPreferences.

With Preferences DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)

When migrating to Proto DataStore, you’ll have to implement a mapping function that defines how to migrate from the key-value pairs used by SharedPreferences to the DataStore schema you defined.

With Proto DataStore:

val settingsDataStore: DataStore<Settings> = context.createDataStore(
    produceFile = { File(context.filesDir, "settings.preferences_pb") },
    serializer = SettingsSerializer,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "settings_preferences"            
        ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
            // Map your sharedPrefs to your type here
          }
    )
)

Wrap-up

SharedPreferences comes with several drawbacks: a synchronous API that can appear safe to call on the UI thread, no mechanism for signaling errors, lack of transactional API, and more. DataStore is a replacement for SharedPreferences that addresses most of these shortcomings. DataStore includes a fully asynchronous API using Kotlin coroutines and Flow, handles data migration, guarantees data consistency, and handles data corruption.

As DataStore is still in alpha, we need your help to make it better! To get started, find out more about DataStore in our documentation and try it out by taking our codelabs: Preferences DataStore codelab and Proto DataStore codelab. Then, let us know how we can improve the library by creating issues on the Issue Tracker.

Announcing Jetpack Compose Alpha!

Posted by Karen Ng, Director, Product Management

Today, we’re releasing the alpha of Jetpack Compose, our modern UI toolkit designed to help you quickly and easily build beautiful apps across all Android platforms, with native access to the platform APIs. Bring your app to life with dramatically less code, interactive tools, and intuitive Kotlin APIs.

No matter where you’re working from -- whether it’s your kitchen table or an office, we know you need a programming language, an IDE and a powerful UI framework that can save you time and reduce how much code you need to write. So we built Jetpack Compose to make you (and us!) more productive with building UI.

We started with Android Jetpack — taking the hardest, most common developer problems on Android and creating a suite of libraries that ensure high quality apps that work across all versions of the platform. Today, 84% of the top 10,000 apps in the Play store are using a Jetpack library.

Then we heard how developers love Kotlin, with over 70% of the top 1000 apps and 60% of pro Android developers using Kotlin today. The Google Home app saw, in certain instances, an 80% reduction in lines of code by using Kotlin and a decrease of NullPointerExceptions by 33% compared to a similar past period. Duolingo, saw reduced line count by an average of 30%.

Finally, we heard strong feedback from the community that developers like the simplicity of declarative APIs for building UI. Jetpack Compose combines all three of these: APIs for high quality apps at scale, an intuitive language, and a reactive programming model.

Jetpack

Jetpack Compose: Now in Alpha

Jetpack Compose Alpha has what you need to build full-fledged Android apps, including powerful tools and interoperability with existing Android views so you don’t need to rewrite your app. Compose APIs are designed and developed hand-in-hand with a set of canonical sample apps that use Material Design that we’re excited to release today! You can import and explore the latest samples directly in Android Studio as well.

compose

The alpha release includes:

  • Animations
  • Constraint Layout
  • Initial A11Y support
  • Input and Gestures
  • Interoperability with Views (start mixing Composable functions in your existing app)
  • Lazy Lists
  • Material UI components
  • Performance optimizations
  • Testing
  • Text and editable Text
  • Theming and Graphics
  • Window management

We've also added a number of new capabilities to Android Studio 4.2 canary, in close partnership with the JetBrains Kotlin team, to help you build apps with Compose:

  • Compose Code completion
  • Compose Preview Annotations
  • Deploy individual composables to any device
  • Interactive Compose previews
  • Kotlin compiler plugin for code generation
  • Sample Data API for Compose

Thinking in Compose

Compose uses a programming model that is quite different from the existing model of building UI on Android. Historically, an Android view hierarchy has been represented as a tree of UI widgets. As the state of the app changes, the UI hierarchy needs to be updated to display the current data. The most common way of updating the UI is to walk the tree using functions like findViewById(), and change nodes by calling methods like:

 button.setText(String) 
container.addView(View) 
 img.setImageBitmap(Bitmap) 
These methods change the internal state of the widget. Not only can this be tedious, but updating views manually increases the likelihood of errors (e.g. forgetting to update a view).

Jetpack Compose is a fully declarative component-based approach, meaning you describe your UI as functions that transform data into a UI hierarchy. When the underlying data changes, the Compose framework automatically updates the UI hierarchy for you, making it simple to build UIs easily and quickly.

Full interop with existing Android views

Adopting any new framework is a big change for existing projects and codebases, which is why we’ve designed Compose to be as easy to adopt as Kotlin — it is fully interoperable with existing Android code, from day one.

Migrating to Compose depends on you and your team. If you're building a new app, the best option might be to implement your entire UI with Compose. We know that most of you have large existing codebases, so rather than rewriting your app, you can combine Compose with your existing UI design.

There are two main ways you can combine Compose with a view-based UI:

  • You can add Compose elements into your existing UI, either by creating an entirely new Compose-based screen, or by adding Compose elements into an existing fragment or view layout.
  • You can add a view-based UI element into your composable functions. Doing so lets you add non-Compose widgets, such as MapView or WebView, into a Compose-based design.

We’ve also published a new library, MDC Compose Theme Adapter, which allows you to reuse your existing Material Components themes within your Compose UI.

To learn more, try the Compose for existing apps codelab or check out these two samples:

  • Tivi and Sunflower are existing apps which are being integrated with Compose
  • Crane sample app, embeds a MapView in Compose

Powerful Tools

Jetpack Compose is built with powerful tooling in Android Studio, designed to help you iterate quickly on the piece of UI you’re working on.

The Compose layout preview enables you to preview your Compose components without having to deploy your app to a device or emulator. As you develop your app, your previews update to help you review your changes faster. To create a layout preview, write a composable function that does not take any parameters, and add the

 @Preview annotation 
After you build your app, the preview function's UI appears in Studio's Preview pane.

Jetpack

Android Studio provides an interactive preview mode. While you're in interactive preview mode, you can click or type in your UI elements, and the UI responds as if it were in the installed app.

Jetpack

You can also deploy a single composable to your physical device or Android Emulator. Android Studio creates a new activity containing the UI generated by that function, and deploys it to your app on the device. This lets you try out the UI on an actual device without needing to reinstall the entire app or navigate to its location.

Jetpack

Get started with Jetpack Compose

To get started with Jetpack Compose, try the Compose Tutorial and get setup. Or dive right into the sample apps and walk through those apps in ‘Compose by Example’:

To find a comprehensive set of Compose resources, from new codelabs and expanded documentation, see the Compose pathway.

Since we open-sourced Jetpack Compose last year, so many of you have given us invaluable feedback, logged bugs, or contributed CLs and have gotten us where we are today. Thank you!

Compose isn’t recommended for full production use yet, in particular as we work towards API stability and finish performance optimizations, but we’d love you to give it a try and share feedback. Join us in the discussion on the #compose channel at Kotlin Slack. Compose 1.0 is expected in 2021.

Happy Composing!

11 Weeks of Android: Jetpack

Posted by Diana Wong, Product Manager, Android Jetpack

Android Jetpack Week 6 banner

This blog post is part of a weekly series for #11WeeksOfAndroid. For each of the #11WeeksOfAndroid, we’re diving into a key area so you don’t miss anything.This week, we spotlighted Jetpack; here’s a look at what you should know.

The big news

In 2018, we launched Android Jetpack as a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across Android versions and devices. We are excited about the growth we’ve seen and the incredible feedback that developers like you have shared with us. 47% of the top 1000 apps use 2 or more Jetpack libraries, not including core libraries like AppCompat or Lifecycle. Our work over the past year has been about making the basics easy for Android developers, so that you can focus on the code you care about. We have released many updates to our existing libraries as well as new libraries to help make building high-quality apps easier.

What to watch

We have also been busy pushing out many updates over the past year!

For an overall look at what’s new in Jetpack, be sure to check out our talk from #Android11 Beta launch:

It’s a quick fly-by introducing many of the updates to our libraries, with pointers on how to get started.

This week, we’ve also done deep dives into major releases like Hilt, including cheat sheets to help you get started, and how we migrated our own samples to use Hilt for dependency injection. Less boilerplate = more fun.

Paging 3.0 is one of our first libraries written Kotlin-first and based on coroutines. The Paging library adds the features you asked for like better error handling, easier list transformations like map or filter, and support for common features like list separators, headers, and footers. We added RxJava, LiveData and ListenableFutures support and backwards compatibility with Paging 2, so it’s easier to migrate.

Using the Camera in your app? CameraX is in Beta and helps developers manage edge cases across different devices and OS versions, so that you don’t have to.

This year, we've made several major improvements with the release of Navigation 2.3, which allows you to navigate between different screens of your app with ease while also allowing you to follow Android UI principles. Let us navigate you through them all here:

Spotlight on Permissions

In Android 11, we continued our work to give users even more control over sensitive permissions. At the same time, it's very important to us that we make it as easy as possible for you as developers to build for Android. With the changes in privacy over the past several releases, Android Jetpack is making it easier for your app to work with Permissions. Now there are type-safe contracts for common intents and more via new ActivityResult APIs. These changes simplify how you request permissions, and we’ll continue to work on making permissions easier in the future. Find out more in this post.

Learning path

Take a look at our new Learning pathway for an easy way to go through all the highlights from this week. It’s an ordered tutorial which guides you through our new content, culminating in a quiz. Bonus: You earn a bright and shiny Jetpack badge to be saved to your Google Developer Profile. In addition to the learning pathway, we’ve also got a new library explorer to make it simple to find more about Jetpack libraries you might be looking for and their latest updates.

Key takeaways

Best practices are baked into Jetpack libraries, giving opinionated guidance to make it easier for you to build a higher-quality Android app. We’ve released new features to Navigation and Workmanager, updates to increase the stability of CameraX, added robustness for Biometrics, and more. We’ve also launched new libraries, like our collaboration with Dagger for Hilt and a new library to help improve app startup. Your feedback is important to us; so give these libraries a shot, tell us what you think, and help us improve them!

Resources

You can find the entire playlist of #11WeeksOfAndroid video content here, and learn more about each week here. We’ll continue to spotlight new areas each week, so keep an eye out and follow us on Twitter and YouTube. Thanks so much for letting us be a part of this experience with you!

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

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

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

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

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

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

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

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

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Decrease startup time with Jetpack App Startup

Posted by Yacine Rezgui, Developer Advocate and Rahul Ravikumar, Software Engineer

Jetpack image

Application startup time is a critical metric for any application. Users expect apps to be responsive and fast to load. When an application does not meet this expectation, it can be disappointing to users. This poor experience may cause a user to rate your app badly on the Play store, or even abandon your app altogether.

Jetpack App Startup is a library that provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Apps and libraries often rely on having components (WorkManager, ProcessLifecycleObserver, FirebaseApp etc.) initialized before Application.onCreate(). This is usually achieved by using content providers to initialize each dependency. Instead of defining separate content providers for each component that needs to be initialized, App Startup lets you define initializers that share a single content provider. This significantly improves app startup time, usually by ~2ms per content provider. App Startup also helps you further improve startup performance by making it really easy to initialize components lazily. When App Startup goes stable, we will be updating our libraries like `WorkManager` and `ProcessLifecycle` to benefit from this as well.

App Startup supports API level 14 and above.

How to use it

Gradle setup

To use App Startup in your library or app, add the following dependency to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
}
Define an Initializer

To be able to use App Startup in your application, you need to define an Initializer. This is where you define how to initialize and specify your dependencies. Here’s the interface you need to implement:

interface Initializer<out T: Any> {
    fun create(context: Context): T
    fun dependencies(): List<Class<out Initializer<*>>>
}

As a practical example, here’s what an Initializer that initializes WorkManager might look like:

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .build()

        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
   
    // This component does not have any dependencies
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

Note: This example is purely illustrative. This Initializer should actually be defined by the WorkManager library.

Lastly, we need to add an entry for WorkManagerInitializer in the AndroidManifest.xml:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes WorkManagerInitializer discoverable. -->
    <meta-data android:name="com.example.WorkManagerInitializer"
          android:value="androidx.startup" />
</provider>

How it works

App Startup uses a single content provider called InitializationProvider. This content provider discovers initializers by introspecting the <meta-data> entries in the merged AndroidManifest.xml file. This happens before Application.onCreate().

After the discovery phase, it subsequently initializes a component after having initialized all its dependencies. Therefore, a component is only initialized after all its dependencies have been initialized.

Lazy initialization

We highly recommend using lazy initialization to further improve startup performance. To make initialization of a component lazy, you need to do the following:

Add a tools:node="remove" attribute to the <meta-data> entry for the Initializer. This disables eager initialization.

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- disables eager initialization -->
    <meta-data android:name="com.example.WorkManagerInitializer"
              tools:node="remove" />
</provider>

To lazily initialize WorkManagerInitializer you can then use:

// This returns an instance of WorkManager
AppInitializer.getInstance(context)
    .initializeComponent(WorkManagerInitializer.class);

Your app now initializes the component lazily. For more information, please read our detailed documentation here.

Final thoughts

App Startup is currently in alpha-02. Find out more about how to use it from our documentation. Once you try it out, help us make it better by giving us feedback on the issue tracker.

Decrease startup time with Jetpack App Startup

Posted by Yacine Rezgui, Developer Advocate and Rahul Ravikumar, Software Engineer

Jetpack image

Application startup time is a critical metric for any application. Users expect apps to be responsive and fast to load. When an application does not meet this expectation, it can be disappointing to users. This poor experience may cause a user to rate your app badly on the Play store, or even abandon your app altogether.

Jetpack App Startup is a library that provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Apps and libraries often rely on having components (WorkManager, ProcessLifecycleObserver, FirebaseApp etc.) initialized before Application.onCreate(). This is usually achieved by using content providers to initialize each dependency. Instead of defining separate content providers for each component that needs to be initialized, App Startup lets you define initializers that share a single content provider. This significantly improves app startup time, usually by ~2ms per content provider. App Startup also helps you further improve startup performance by making it really easy to initialize components lazily. When App Startup goes stable, we will be updating our libraries like `WorkManager` and `ProcessLifecycle` to benefit from this as well.

App Startup supports API level 14 and above.

How to use it

Gradle setup

To use App Startup in your library or app, add the following dependency to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
}
Define an Initializer

To be able to use App Startup in your application, you need to define an Initializer. This is where you define how to initialize and specify your dependencies. Here’s the interface you need to implement:

interface Initializer<out T: Any> {
    fun create(context: Context): T
    fun dependencies(): List<Class<out Initializer<*>>>
}

As a practical example, here’s what an Initializer that initializes WorkManager might look like:

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .build()

        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
   
    // This component does not have any dependencies
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

Note: This example is purely illustrative. This Initializer should actually be defined by the WorkManager library.

Lastly, we need to add an entry for WorkManagerInitializer in the AndroidManifest.xml:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes WorkManagerInitializer discoverable. -->
    <meta-data android:name="com.example.WorkManagerInitializer"
          android:value="androidx.startup" />
</provider>

How it works

App Startup uses a single content provider called InitializationProvider. This content provider discovers initializers by introspecting the <meta-data> entries in the merged AndroidManifest.xml file. This happens before Application.onCreate().

After the discovery phase, it subsequently initializes a component after having initialized all its dependencies. Therefore, a component is only initialized after all its dependencies have been initialized.

Lazy initialization

We highly recommend using lazy initialization to further improve startup performance. To make initialization of a component lazy, you need to do the following:

Add a tools:node="remove" attribute to the <meta-data> entry for the Initializer. This disables eager initialization.

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- disables eager initialization -->
    <meta-data android:name="com.example.WorkManagerInitializer"
              tools:node="remove" />
</provider>

To lazily initialize WorkManagerInitializer you can then use:

// This returns an instance of WorkManager
AppInitializer.getInstance(context)
    .initializeComponent(WorkManagerInitializer.class);

Your app now initializes the component lazily. For more information, please read our detailed documentation here.

Final thoughts

App Startup is currently in alpha-02. Find out more about how to use it from our documentation. Once you try it out, help us make it better by giving us feedback on the issue tracker.