Tag Archives: Learn

Order Files in Android

Posted by Aditya Kumar – Software Engineer


Binary layout using a symbol order file (also known as binary order file or linker order file) is a well-known link-time optimization. The linker uses the order of symbols in order file to lay out symbols in the binary. Order file based binary layout improves application launch time as well as other critical user journeys. Order file generation is typically a multi-step process where developers use different tools at every stage. We are providing a unified set of tools and documentation that will allow every native app developer to leverage this optimization. Both Android app developers and the AOSP community can benefit from the tools.


Source code is typically structured to facilitate software development and comprehension. The layout of functions and variables in a binary is also impacted by their relative ordering in the source code. The binary layout impacts application performance as the operating system has no way of knowing which symbols will be required in future and typically uses spatial locality as one of the cost models for prefetching subsequent pages.

But the order of symbols in a binary may not reflect the program execution order. When an application executes, fetching symbols that are not present in memory would result in page faults. For example, consider the following program:

// Test.cpp
int foo() { /* */ } int bar() { /* */ } // Other functions... int main() { bar(); foo();


Which gets compiled into:

# Test.app page_x: _foo page_y: _bar # Other symbols page_z:_main

When Test.app starts, its entrypoint _main is fetched first then _bar followed by _foo. Executing Test.app can lead to page faults for fetching each function. Compare this to the following binary layout where all the functions are located in the same page (assuming the functions are small enough).

# Test.app page_1: _main page_1: _bar page_1: _foo # Other symbols

In this case when _main gets fetched, _bar and _foo can get fetched in the memory at the same time. In case these symbols are large and they are located in consecutive pages, there is a high chance the operating system may prefetch those pages resulting in less page faults.

Because execution order of functions during an application lifecycle may depend on various factors it is impossible to have a unique order of symbols that is most efficient. Fortunately, application startup sequence is fairly deterministic and stable in general. And it is also possible to build a binary having a desired symbol order with the help of linkers like lld which is the default linker for Android NDK toolchain.

Order file is a text file containing a list of symbols. The linker uses the order of symbols in order file to lay out symbols in the binary. An order file having functions that get called during the app startup sequence can reduce page faults resulting in improved launch time. Order files can improve the launch time of mobile applications by more than 2%. The benefits of order files are more meaningful on larger apps and lower end devices. A more mature order file generation system can improve other critical user journeys.


The order file generation involves the following steps

    • Collect app startup sequence using compiler instrumentation technique
      • Use compiler instrumentation to report every function invocation
      • Run the instrumented binary to collect launch sequence in a (binary) profraw file
    • Generate order file from the profraw files
    • Validate order file
    • Merge multiple order files into one
    • Recompile the app with the merged order file


The order file generation is based on LLVM’s compiler instrumentation process. LLVM has a stage to generate the order file then recompile the source code using the order file.ALT TEXT

Collect app startup sequence

The source code is instrumented by passing -forder-file-instrumentation to the compiler. Additionally, the -orderfile-write-mapping flag is also required for the compiler to generate a mapping file. The mapping file is generated during compilation and it is used while processing the profraw file. The mapping file shows the mapping from MD5 hash to function symbol (as shown below).

# Mapping file MD5 db956436e78dd5fa main MD5 83bff1e88ac48f32 _GLOBAL__sub_I_main.cpp MD5 c943255f95351375 _Z5mergePiiii MD5 d2d2238cf08db816 _Z9mergeSortPiii MD5 11ed18006e729e73 _Z4partPiii MD5 3e897b5ee8bebbd1 _Z9quickSortPiii

The profile (profraw file) is generated every time the instrumented application is executed. The profile data in the profraw file contains the MD5 hash of the functions executed in chronological order. The profraw file does not have duplicate entries because each function only outputs its MD5 hash on first invocation. A typical run of binary containing the functions listed in the mapping file above can have the following profraw entries.

# Profraw file 00000000 32 8f c4 8a e8 f1 bf 83 fa d5 8d e7 36 64 95 db |2...........6d..| 00000010 16 b8 8d f0 8c 23 d2 d2 75 13 35 95 5f 25 43 c9 |.....#..u.5._%C.| 00000020 d1 bb be e8 5e 7b 89 3e 00 00 00 00 00 00 00 00 |....^{.>........| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

In order to find the function names corresponding to the MD5 hashes in a profraw file, a corresponding mapping file is used.

Note: The compiler instrumentation for order files (-forder-file-instrumentation) only works when an optimization flag (01, 02, 03, 0s, 0z) is passed. So, if -O0 (compiler flag typically used for debug builds) is passed, the compiler will not instrument the binary. In principle, one should use the same optimization flag for instrumentation that is used in shipping release binaries.

The Android NDK repository has scripts that automate the order file generation given a mapping file and an order file.

Recompiling with Order File

Once you have an order file, you provide the path of the order file to the linker using the --symbol-ordering-file flag.

Detailed design

Creating Order File Build Property

The Android Open Source Project (AOSP) uses a build system called soong so we can leverage this build system to pass the flags as necessary. The order file build property has four main fields:

    • instrumentation
    • load_order_file
    • order_file_path
    • cflags

The cflags are meant to add other necessary flags (like mapping flags) during compilation. The load_order_file and order_file_path tells the build system to recompile with the order file rather than set it to the profiling stage. The order files must be in saved in one of two paths:

    • toolchain/pgo-profiles/orderfiles
    • vendor/google_data/pgo_profile/orderfiles

# Profiling orderfile: { instrumentation: true, load_order_file: false, order_file_path: "", cflags: [ "-mllvm", "-orderfile-write-mapping=<filename>-mapping.txt", ], } #Recompiling with Order File orderfile: { instrumentation: true, load_order_file: true, order_file_path: "<filename>.orderfile", }

Creating order files

We provide a python script to create an order file from a mapping file and a profraw file. The script also allows removing a particular symbol or creating an order file until a particular symbol.

Script Flags:

        • Profile file (--profile-file):
                • Description: The profile file generated by running a binary compiled with -forder-file-instrumentation
        • Mapping file (--mapping-file):
                • Description: The mapping file generated during compilation that maps MD5 hashes to symbol names
        • Output file (--output):
                • Description: The output file name for the order file. Default Name: default.orderfile
        • Deny List (--denylist):
                • Description: Symbols that you want to exclude from the order file
        • Last symbol (--last-symbol):
                • Description: The order file will end at the passed last symbol and ignore the symbols after it. If you want an order file only for startup, you should pass the last startup symbol. Last-symbol has priority over leftover so we will output until the last symbol and ignore the leftover flag.
        • Leftover symbols (--leftover):
                • Description: Some symbols (functions) might not have been executed so they will not appear in the profile file. If you want these symbols in your order file, you can use this flag and it will add them at the end.

Validating order files

Once we get an order file for a library or binary, we need to check if it is valid based on a set of criteria. Some order files may not be of good quality so they are better discarded. This can happen due to several reasons like application terminated unexpectedly, the runtime could not write the complete profraw file before exiting, an undesired code-sequence was collected in the profile, etc. To automate this process, we provide a python script that can help developers check for:

    • Partial order that needs to be in the order file
    • Symbols that have to be present in order file
    • Symbols that should not be present in order file
    • Minimum number of symbols to make an order file

Script Flags:

        • Order file (--order-file):
                • Description: The order file you are validating on the below criteria.
        • Partial Order (--partial):
                • Description: A partial order of symbols that must be held in the order file.
        • Allowed Lists (--allowlist):
                • Description: Symbols that must be present in the order file.
        • Denied Lists (--denylist):
                • Description: Symbols that should not be in the order file. Denylist flag has priority over allowlist.
        • Minimum Number of Entries (--min):
                • Description: Minimum number of symbols needed for an order file

Merging orderfiles

At a higher level, the order file symbols in a collection of order files approximate a partial order (poset) of function names with order defined by time of execution. Across different runs of an application, the order files might have variations. These variations could be due to OS, device class, build version, user configurations etc. However, the linker can only take one order file to build an application. In order to have one order file that provides the desired benefits, we need to merge these order files into a single order file. The merging algorithm also needs to be efficient so as to not slow down the build time. There are non-linear clustering algorithms that may not scale well for merging large numbers of order files, each having many symbols. We provide an efficient merging algorithm that converges quickly. The algorithm allows for customizable parameters, such that developers can tune the outcome.

Merging N partial order sets can be done either pessimistically (merging a selection of order files all the way until there is one order file left) or optimistically (merging all of them at once). The pessimistic approach can be inefficient as well as sub-optimal. As a result, it is better to work with all N partial order sets at once. In order to have an efficient implementation it helps to represent all N posets with a weighted directed Graph (V,E) where:

    • V: Elements of partial order sets (symbols) and the number of times it appears in different partial order sets. Note that the frequency of vertices may be greater than the sum of all incoming edges because of invocations from uninstrumented parts of binary, dependency injection etc.
    • E (V1 -> V2): An edge occurs if the element of V2 immediately succeeds V1 in any partial order set with its weight being the number of times this happens.

For a binary executable, there is one root (e.g., main) vertex, but shared libraries might have many roots based on which functions are called in the binary using them. The graph gets complicated if the application has threads as they frequently result in cycles. To have a topological order, cycles are removed by preferring the highest probability path over others. A Depth-First traversal that selects the highest weighted edge serves the purpose.

Removing Cycles:

- Mark back edges during a Depth-First traversal - For each Cycle (c):      - Add the weights of all in-edges of each vertex (v) in the cycle excluding the edges in the cycle      - Remove the cycle edge pointing **to** the vertex with highest sum

After cycles are removed, the same depth first traversal gives a topological order (the order file) when all the forward edges are removed. Essentially, the algorithm computes a minimum-spanning-tree of maximal weights and traverses the tree in topological order.

Producing an order:

printOrderUtil(G, n, order):    - If n was visited:         - return    - Add n to the end of order    - Sort all out edges based on weight    - For every out_edge (n, v):        - printOrderUtil(G, v, order) printOrder(G):    - Get all roots    - order = []    - For each root r:        - printOrderUtil(G, r, order)    - return order


Given the following order files:

    • main -> b -> c -> d
    • main -> a -> c
    • main -> e -> f
    • main -> b
    • main -> b
    • main -> c -> b
Flow diagram of orderfiles

The graph to the right is obtained by removing cycles.

    • DFS: main -> b-> c -> b
    • Back edge: c -> b
    • Cycle: b -> c-> b
    • Cycle edges: [b -> c, c -> b]
    • b’s sum of in-edges is 3
    • c’s sum of in-edges is 2
    • This implies b will be traversed from a higher frequency edge, so c -> b is removed
    • Ignore forward edges a->c, main->c
    • The DFS of the acyclic graph on the right will produce an order file main -> b -> c -> d -> a -> e -> f after ignoring the forward edges.

Collecting order files for Android Apps (Java, Kotlin)

The order file instrumentation and profile data collection is only enabled for C/C++ applications. As a result, it cannot benefit Java or Kotlin applications. However, Android apps that ship compiled C/C++ libraries can benefit from order file.

To generate order file for libraries that are used by Java/Kotlin applications, we need to invoke the runtime methods (called as part of order file instrumentation) at the right places. There are three functions that users have to call:

    • __llvm_profile_set_filename(char *f): Set the name of the file where profraw data will be dumped.
    • __llvm_profile_initialize_file: Initialize the file set by __llvm_profile_set_filename
    • __llvm_orderfile_dump: Dumps the profile(order file data) collected while running instrumented binary

Similarly, the compiler and linker flags should be added to build configurations. We provide template build system files e.g, CMakeLists.txt to compile with the correct flags and add a function to dump the order files when the Java/Kotlin application calls it.

# CMakeLists.txt set(GENERATE_PROFILES ON) #set(USE_PROFILE "${CMAKE_SOURCE_DIR}/demo.orderfile") add_library(orderfiledemo SHARED orderfile.cpp) target_link_libraries(orderfiledemo log) if(GENERATE_PROFILES) # Generating profiles require any optimization flag aside from -O0. # The mapping file will not generate and the profile instrumentation does not work without an optimization flag. target_compile_options( orderfiledemo PRIVATE -forder-file-instrumentation -O2 -mllvm -orderfile-write-mapping=mapping.txt ) target_link_options( orderfiledemo PRIVATE -forder-file-instrumentation ) target_compile_definitions(orderfiledemo PRIVATE GENERATE_PROFILES) elseif(USE_PROFILE) target_compile_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) target_link_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) endif()

We also provide a sample app to dump order files from a Kotlin application. The sample app creates a shared library called “orderfiledemo” and invokes the DumpProfileDataIfNeeded function to dump the order file. This library can be taken out of this sample app and can be repurposed for other applications.

// Order File Library #if defined(GENERATE_PROFILES) extern "C" int __llvm_profile_set_filename(const char *); extern "C" int __llvm_profile_initialize_file(void); extern "C" int __llvm_orderfile_dump(void); #endif void DumpProfileDataIfNeeded(const char *temp_dir) { #if defined(GENERATE_PROFILES) char profile_location[PATH_MAX] = {}; snprintf(profile_location, sizeof(profile_location), "%s/demo.output", temp_dir); __llvm_profile_set_filename(profile_location); __llvm_profile_initialize_file(); __llvm_orderfile_dump(); __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Wrote profile data to %s", profile_location); #else __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Did not write profile data because the app was not " "built for profile generation"); #endif } extern "C" JNIEXPORT void JNICALL Java_com_example_orderfiledemo_MainActivity_runWorkload(JNIEnv *env, jobject /* this */, jstring temp_dir) { DumpProfileDataIfNeeded(env->GetStringUTFChars(temp_dir, 0)); }

# Kotlin Application class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) runWorkload(applicationContext.cacheDir.toString()) binding.sampleText.text = "Hello, world!" } /** * A native method that is implemented by the 'orderfiledemo' native library, * which is packaged with this application. */ external fun runWorkload(tempDir: String) companion object { // Used to load the 'orderfiledemo' library on application startup. init { System.loadLibrary("orderfiledemo") } } }


order file generation only works for native binaries. The validation and merging scripts will work for any set of order files.


External References

Full-stack development in Project IDX

Posted by Kaushik Sathupadi, Prakhar Srivastav, and Kristin Bi – Software Engineers; Alex Geboff – Technical Writer

We launched Project IDX, our experimental, new browser-based development experience, to simplify the chaos of building full-stack apps and streamline the development process from (back)end to (front)end.

In our experience, most web applications are built with at-least two different layers: a frontend (UI) layer and a backend layer. When you think about the kind of app you’d build in a browser-based developer workspace, you might not immediately jump to full-stack apps with robust, fully functional backends. Developing a backend in a web-based environment can get clunky and costly very quickly. Between different authentication setups for development and production environments, secure communication between backend and frontend, and the complexity of setting up a fully self-contained (hermetic) testing environment, costs and inconveniences can add up.

We know a lot of you are excited to try IDX yourselves, but in the meantime, we wanted to share this post about full-stack development in Project IDX. We’ll untangle some of the complex situations you might hit as a developer building both your frontend and backend layers in a web-based workspace — developer authentication, frontend-backend communication, and hermetic testing — and how we’ve tried to make it all just a little bit easier. And of course we want to hear from you about what else we should build that would make full-stack development easier for you!

Streamlined app previews

First and foremost, we've streamlined the process of enabling your applications frontend communication with its backend services in the VM, making it effortless to preview your full-stack application in the browser.

IDX workspaces are built on Google Cloud Workstations and securely access connected services through Service Accounts. Each workspace’s unique service account supports seamless, authenticated preview environments for your applications frontend. So, when you use Project IDX, application previews are built directly into your workspace, and you don’t actually have to set up a different authentication path to preview your UI. Currently, IDX only supports web previews, but Android and iOS application previews are coming soon to IDX workspaces near you.

Additionally, if your setup necessitates communication with the backend API under development in IDX from outside the browser preview, we've established a few mechanisms to temporarily provide access to the ports hosting these API backends.

Simple front-to-backend communication

If you’re using a framework that serves both the backend and frontend layers from the same port, you can pass the $PORT flag to use a custom PORT environment variable in your workspace configuration file (powered by Nix and stored directly in your workspace). This is part of the basic setup flow in Project IDX, so you don’t have to do anything particularly special (outside of setting the variable in your config file). Here’s an example Nix-based configuration file:

{ pkgs, ... }: {

# NOTE: This is an excerpt of a complete Nix configuration example.

# Enable previews and customize configuration
idx.previews = {
  enable = true;
  previews = [
      command = [
      manager = "web";
      id = "web";

However, if your backend server is running on a different port from your UI server, you’ll need to implement a different strategy. One method is to have the frontend proxy the backend, as you would with Vite's custom server options.

Another way to establish communication between ports is to set up your code so the javascript running on your UI can communicate with the backend server using AJAX requests.

Let’s start with some sample code that includes both a backend and a frontend. Here’s a backend server written in Express.js:

import express from "express";
import cors from "cors";

const app= express();

app.get("/", (req, res) => {
    res.send("Hello World");

app.listen(6000, () => {
    console.log("Server is running on port 6000");

The bolded line in the sample — app.use(cors()); — sets up the CORS headers. Setup might be different based on the language/framework of your choice, but your backend needs to return these headers whether you’re developing locally or on IDX.

When you run the server in the IDX terminal, the backend ports show up in the IDX panel. And every port that your server runs on is automatically mapped to a URL you can call.

Moving text showing the IDX terminal and panel

Now, let's write some client code to make an AJAX call to this server.

// This URL is copied from the side panel showing the backend ports view
const WORKSPACE_URL = "https://6000-monospace-ksat-web-prod-79679-1677177068249.cluster-lknrrkkitbcdsvoir6wqg4mwt6.cloudworkstations.dev/";

async function get(url) {
  const response = await fetch(url, {
    credentials: 'include',

// Call the backend

We’ve also made sure that the fetch() call includes credentials. IDX URLs are authenticated, so we need to include credentials. This way, the AJAX call includes the cookies to authenticate against our servers.

If you’re using XMLHttpRequest instead of fetch, you can set the “withCredentials” property, like this:

const xhr = new XMLHttpRequest();
xhr.open("GET", WORKSPACE_URL, true);
xhr.withCredentials = true;

Your code might differ from our samples based on the client library you use to make the AJAX calls. If it does, check the documentation for your specific client library on how to make a credentialed request. Just be sure to make a credentialed request.

Server-side testing without a login

In some cases you might want to access your application on Project IDX without logging into your Google account — or from an environment where you can’t log into your Google account. For example, if you want to access an API you're developing in IDX using either Postman or cURL from your personal laptops's command line. You can do this by using a temporary access token generated by Project IDX.

Once you have a server running in Project IDX, you can bring up the command menu to generate an access token. This access token is a short-lived token that temporarily allows you to access your workstation.

It’s extremely important to note that this access token provides access to your entire IDX workspace, including but not limited to your application in preview, so you shouldn’t share it with just anyone. We recommend that you only use it for testing.

Generate access token in Project IDX

When you run this command from IDX, your access token shows up in a dialog window. Copy the access token and use it to make a cURL request to a service running on your workstation, like this one:

$ export ACCESS_TOKEN=myaccesstoken
$ curl -H "Authorization: Bearer $ACCESS_TOKEN" https://6000-monospace-ksat-web-prod-79679-1677177068249.cluster-lknrrkkitbcdsvoir6wqg4mwt6.cloudworkstations.dev/
Hello world

And now you can run tests from an authenticated server environment!

Web-based, fully hermetic testing

As we’ve highlighted, you can test your application’s frontend and backend in a fully self-contained, authenticated, secure environment using IDX. You can also run local emulators in your web-based development environment to test your application’s backend services.

For example, you can run the Firebase Local Emulator Suite directly from your IDX workspace. To install the emulator suite, you’d run firebase init emulators from the IDX Terminal tab and follow the steps to configure which emulators you want on what ports.


Once you’ve installed them, you can configure and use them the same way you would in a local development environment from the IDX terminal.

Next Steps

As you can see, Project IDX can meet many of your full-stack development needs — from frontend to backend and every emulator in between.

If you're already using Project IDX, tag us on social with #projectidx to let us know how Project IDX has helped you with your full-stack development. Or to sign up for the waitlist, visit idx.dev.

Make the passkey endpoints well-known URL part of your passkey implementation

Posted by Amy Zeppenfeld – Developer Relations Engineer

Passkeys are leading the charge towards a more secure future without passwords. Passkeys are a new type of cryptographic credential that leverages FIDO2 and WebAuthn to provide an authentication mechanism that is phishing-resistant, user friendly, simple to implement, and more secure than password-based authentication. Most major operating systems and browsers now feature full passkey support. Passkeys are expected to replace passwords as the predominant authentication mechanism in the not-too-distant future, and developers are advised to begin implementing passkey-enabled authentication solutions today.

As you implement passkeys in your app or web service, take a moment to implement a passkey endpoints well-known URL.

This is a standardized way to advertise your support for passkeys and optimize user experience. This well-known URL will allow third party services like password managers, passkey providers, and other security tools to direct users to enroll and manage their passkeys for any site that supports them. You can use app-links or deep linking with the passkey-endpoints well-known URL to allow these pages to open directly in your app.

Password management tool usage has been steadily rising, and we expect most providers will integrate passkey management as well. You can allow third party tools and services to direct your users to your dedicated passkey management page by implementing the passkey-endpoints well-known URL.

The best part is that in most cases you can implement this feature in two hours or less! All you need to do is host a simple schema on your site. Check out the example below:

  1. For a web service at https://example.com, the well-known URLwould be https://example.com/.well-known/passkey-endpoints
  2. When the URL is queried, the response should use the following schema:
{ "enroll": "https://example.com/account/manage/passkeys/create", "manage": "https://example.com/account/manage/passkeys" }

Note: You can decide the exact value of the URLs for both enroll and manage based on your website’s own configuration.

If you have a mobile app, we strongly recommend utilizing deep linking to have these URLs open the corresponding screen for each activity directly in your app to “enroll” or “manage” passkeys. This will keep your users focused and on track to enroll into passkeys.

And that’s it!

Further details and examples can be found in the passkey endpoints well-known URL explainer.

How KAYAK reduced sign in time by 50% and improved security with passkeys

Posted by Kateryna Semenova, Developer Relations Engineer, Android


KAYAK is one of the world's leading travel search engines that helps users find the best deals on flights, hotels, and rental cars. In 2023, KAYAK integrated passkeys - a new type of passwordless authentication - into its Android and web apps. As a result, KAYAK reduced the average time it takes their users to sign-up and sign-in by 50%, and also saw a decrease in support tickets.

This case study explains KAYAK's implementation on Android with Credential Manager API and RxJava. You can use this case study as a model for implementing Credential Manager to improve security and user experience in your own apps.

If you want a quick summary, check out the companion video on YouTube.


Like most businesses, KAYAK has relied on passwords in the past to authenticate users. Passwords are a liability for both users and businesses alike: they're often weak, reused, guessed, phished, leaked, or hacked.

“Offering password authentication comes with a lot of effort and risk for the business. Attackers are constantly trying to brute force accounts while not all users understand the need for strong passwords. However, even strong passwords are not fully secure and can still be phished.” – Matthias Keller, Chief Scientist and SVP, Technology at KAYAK

To make authentication more secure, KAYAK sent "magic links" via email. While helpful from a security standpoint, this extra step introduced more user friction by requiring users to switch to a different app to complete the login process. Additional measures needed to be introduced to mitigate the risk of phishing attacks.


KAYAK's Android app now uses passkeys for a more secure, user-friendly, and faster authentication experience. Passkeys are unique, secure tokens that are stored on the user's device and can be synchronized across multiple devices. Users can sign in to KAYAK with a passkey by simply using their existing device's screen lock, making it simpler and more secure than entering a password.

“We've added passkeys support to our Android app so that more users can use passkeys instead of passwords. Within that work, we also replaced our old Smartlock API implementation with the Sign in with Google supported by Credential Manager API. Now, users are able to sign up and sign in to KAYAK with passkeys twice as fast as with an email link, which also improves the completion rate" – Matthias Keller, Chief Scientist and SVP, Technology at KAYAK

Credential Manager API integration

To integrate passkeys on Android, KAYAK used the Credential Manager API. Credential Manager is a Jetpack library that unifies passkey support starting with Android 9 (API level 28) and support for traditional sign-in methods such as passwords and federated authentication into a single user interface and API.

Image of Credential Manager's passkey creation screen.
Figure 1: Credential Manager's passkey creation screens.

Designing a robust authentication flow for apps is crucial to ensure security and a trustworthy user experience. The following diagram demonstrates how KAYAK integrated passkeys into their registration and authentication flows:

Flow diagram of KAYAK's registration and authentication processes
Figure 2:KAYAK's diagram showing their registration and authentication flows.

At registration time, users are given the opportunity to create a passkey. Once registered, users can sign in using their passkey, Sign in with Google, or password. Since Credential Manager launches the UI automatically, be careful not to introduce unexpected wait times, such as network calls. Always fetch a one-time challenge and other passkeys configuration (such as RP ID) at the beginning of any app session.

While the KAYAK team is now heavily invested in coroutines, their initial integration used RxJava to integrate with the Credential Manager API. They wrapped Credential Manager calls into RxJava as follows:

override fun createCredential(request: CreateCredentialRequest, activity: Activity): Single<CreateCredentialResponse> { return Single.create { emitter -> // Triggers credential creation flow credentialManager.createCredentialAsync( request = request, activity = activity, cancellationSignal = null, executor = Executors.newSingleThreadExecutor(), callback = object : CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { override fun onResult(result: CreateCredentialResponse) { emitter.onSuccess(result) } override fun onError(e: CreateCredentialException) { emitter.tryOnError(e) } } ) } }

This example defines a Kotlin function called createCredential() that returns a credential from the user as an RxJava Single of type CreateCredentialResponse. The createCredential() function encapsulates the asynchronous process of credential registration in a reactive programming style using the RxJava Single class.

For a Kotlin implementation of this process using coroutines, read the Sign in your user with Credential Manager guide.

New user registration sign-up flow

This example demonstrates the approach KAYAK used to register a new credential, here Credential Manager was wrapped in Rx primitives.

webAuthnRetrofitService .getClientParams(username = /** email address **/) .flatMap { response -> // Produce a passkeys request from client params that include a one-time challenge CreatePublicKeyCredentialOption(/** produce JSON from response **/) } .subscribeOn(schedulers.io()) .flatMap { request -> // Call the earlier defined wrapper which calls the Credential Manager UI // to register a new passkey credential credentialManagerRepository .createCredential( request = request, activity = activity ) } .flatMap { // send credential to the authentication server } .observeOn(schedulers.main()) .subscribe( { /** process successful login, update UI etc. **/ }, { /** process error, send to logger **/ } )

Rx allowed KAYAK to produce more complex pipelines that can involve multiple interactions with Credential Manager.

Existing user sign-in

KAYAK used the following steps to launch the sign-in flow. The process launches a bottom sheet UI element, allowing the user to log in using a Google ID and an existing passkey or saved password.

Image of bottom sheet for passkey authentication
Figure 3:Bottom sheet for passkey authentication.

Developers should follow these steps when setting up a sign-in flow:

  1. Since the bottom sheet is launched automatically, be careful not to introduce unexpected wait times in the UI, such as network calls. Always fetch a one-time challenge and other passkeys configuration (such as RP ID) at the beginning of any app session.
  2. When offering Google sign-in via Credential Manager API, your code should initially look for Google accounts that have already been used with the app. To handle this, call the API with the setFilterByAuthorizedAccounts parameter set to true.
  3. If the result returns a list of available credentials, the app shows the bottom sheet authentication UI to the user.
  4. If a NoCredentialException appears, no credentials were found: No Google accounts, no passkeys, and no saved passwords. At this point, your app should call the API again and set setFilterByAuthorizedAccounts to false to initiate the Sign up with Google flow.
  5. Process the credential returned from Credential Manager.
Single.fromSupplier<GetPublicKeyCredentialOption> { GetPublicKeyCredentialOption(/** Insert challenge and RP ID that was fetched earlier **/) } .flatMap { response -> // Produce a passkeys request GetPublicKeyCredentialOption(response.toGetPublicKeyCredentialOptionRequest()) } .subscribeOn(schedulers.io()) .map { publicKeyCredentialOption -> // Merge passkeys request together with other desired options, // such as Google sign-in and saved passwords. } .flatMap { request -> // Trigger Credential Manager system UI credentialManagerRepository.getCredential( request = request, activity = activity ) } .onErrorResumeNext { throwable -> // When offering Google sign-in, it is recommended to first only look for Google accounts // that have already been used with our app. If there are no such Google accounts, no passkeys, // and no saved passwords, we try looking for any Google sign-in one more time. if (throwable is NoCredentialException) { return@onErrorResumeNext credentialManagerRepository.getCredential( request = GetCredentialRequest(/* Google ID with filterByAuthorizedOnly = false */), activity = activity ) } Single.error(throwable) } .flatMapCompletable { // Step 1: Use Retrofit service to send the credential to the server for validation. Waiting // for the server is handled on a IO thread using subscribeOn(schedulers.io()). // Step 2: Show the result in the UI. This includes changes such as loading the profile // picture, updating to the personalized greeting, making member-only areas active, // hiding the sign-in dialog, etc. The activities of step 2 are executed on the main thread. } .observeOn(schedulers.main()) .subscribe( // Handle errors, e.g. send to log ingestion service. // A subset of exceptions shown to the user can also be helpful, // such as user setup problems. // Check out more info in Troubleshoot common errors at // https://developer.android.com/training/sign-in/passkeys#troubleshoot )

“Once the Credential Manager API is generally implemented, it is very easy to add other authentication methods. Adding Google One-Tap Sign In was almost zero work after adding passkeys.” – Matthias Keller

To learn more, follow the guide on how to Integrate Credentials Manager API and how to Integrate Credential Manager with Sign in with Google.

UX considerations

Some of the major user experience considerations KAYAK faced when switching to passkeys included whether users should be able to delete passkeys or create more than one passkey.

Our UX guide for passkeys recommends that you have an option to revoke a passkey, and that you ensure that the user does not create duplicate passkeys for the same username in the same password manager.

Image of KAYAK's UI for passkey management
Figure 4:KAYAK's UI for passkey management.

To prevent registration of multiple credentials for the same account, KAYAK used the excludeCredentials property that lists credentials already registered for the user. The following example demonstrates how to create new credentials on Android without creating duplicates:

fun WebAuthnClientParamsResponse.toCreateCredentialRequest(): String { val credentialRequest = WebAuthnCreateCredentialRequest( challenge = this.challenge!!.asSafeBase64, relayingParty = this.relayingParty!!, pubKeyCredParams = this.pubKeyCredParams!!, userEntity = WebAuthnUserEntity( id = this.userEntity!!.id.asSafeBase64, name = this.userEntity.name, displayName = this.userEntity.displayName ), authenticatorSelection = WebAuthnAuthenticatorSelection( authenticatorAttachment = "platform", residentKey = "preferred" ), // Setting already existing credentials here prevents // creating multiple passkeys on the same keychain/password manager excludeCredentials = this.allowedCredentials!!.map { it.copy(id = it.id.asSafeBase64) }, ) return GsonBuilder().disableHtmlEscaping().create().toJson(credentialRequest) }

And this is how KAYAK implemented excludeCredentials functionality for their Web implementation.

var registrationOptions = { 'publicKey': { 'challenge': self.base64ToArrayBuffer(data.challenge), 'rp': data.rp, 'user': { 'id': new TextEncoder().encode(data.user.id), 'name': data.user.name, 'displayName': data.user.displayName }, 'pubKeyCredParams': data.pubKeyCredParams, 'authenticatorSelection': { 'residentKey': 'required' } } }; if (data.allowCredentials && data.allowCredentials.length > 0) { var excludeCredentials = []; for (var i = 0; i < data.allowCredentials.length; i++) { excludeCredentials.push({ 'id': self.base64ToArrayBuffer(data.allowCredentials[i].id), 'type': data.allowCredentials[i].type }); } registrationOptions.publicKey.excludeCredentials = excludeCredentials; } navigator.credentials.create(registrationOptions);

Server-side implementation

The server-side part is an essential component of an authentication solution. KAYAK added passkey capabilities to their existing authentication backend by utilizing WebAuthn4J, an open source Java library.

KAYAK broke down the server-side process into the following steps:

  1. The client requests parameters needed to create or use a passkey from the server. This includes the challenge, the supported encryption algorithm, the relying party ID, and related items. If the client already has a user email address, the parameters will include the user object for registration, and a list of passkeys if any exist.
  2. The client runs browser or app flows to start passkey registration or sign-in.
  3. The client sends retrieved credential information to the server. This includes client ID, authenticator data, client data, and other related items. This information is needed to create an account or verify a sign-in.

When KAYAK worked on this project, no third-party products supported passkeys. However, many resources are now available for creating a passkey server, including documentation and library examples.


Since integrating passkeys, KAYAK has seen a significant increase in user satisfaction. Users have reported that they find passkeys to be much easier to use than passwords, as they do not require users to remember or type in a long, complex string of characters. KAYAK reduced the average time it takes their users to sign-up and sign-in by 50%, have seen a decrease in support tickets related to forgotten passwords, and have made their system more secure by reducing their exposure to password-based attacks. Thanks to these improvements, ​​KAYAK plans to eliminate password-based authentication in their app by the end of 2023.

“Passkeys make creating an account lightning fast by removing the need for password creation or navigating to a separate app to get a link or code. As a bonus, implementing the new Credential Manager library also reduced technical debt in our code base by putting passkeys, passwords and Google sign-in all into one new modern UI. Indeed, users are able to sign up and sign in to KAYAK with passkeys twice as fast as with an email link, which also improves the completion rate." – Matthias Keller


Passkeys are a new and innovative authentication solution that offers significant benefits over traditional passwords. KAYAK is a great example of how an organization can improve the security and usability of its authentication process by integrating passkeys. If you are looking for a more secure and user-friendly authentication experience, we encourage you to consider using passkeys with Android's Credential Manager API.

Password manager Dashlane sees 70% increase in conversion rate for signing-in with passkeys compared to passwords

Posted by Milica Mihajlija, Technical Writer

This article was originally posted on Google for Developers

Dashlane is a password management tool that provides a secure way to manage user credentials, access control, and authentication across multiple systems and applications. Dashlane has over 18 million users and 20,000 businesses in 180 countries. It’s available on Android, iOS, macOS, Windows, and as a web app with an extension for Chrome, Firefox, Edge, and Safari.

The opportunity

Many users choose password managers because of the pain and frustration of dealing with passwords. While password managers help here, the fact remains that one of the biggest issues with passwords are security breaches. Passkeys on the other hand bring passwordless authentication with major advancements in security.

Passkeys are a simple and secure authentication technology that enables signing in to online accounts without entering a password. They cannot be reused, don't leak in server breaches of relying parties, and protect users from phishing attacks. Passkeys are built on open standards and work on all major platforms and browsers.

As an authentication tool, Dashlane’s primary goal is to ensure customers’ credentials are kept safe. They realized how significant the impact of passkeys could be to the security of their users and adapted their applications to support passkeys across devices, browsers, and platforms. With passkey support they provide users a secure and convenient access with a phishing-resistant authentication method.


Passkeys as a replacement for passwords is a relatively new concept and to address the challenge of going from a familiar to an unfamiliar way of logging in, the Dashlane team considered various solutions.

On the desktop web they implemented conditional UI support through a browser extension to help users gracefully navigate the choice between using a password and a passkey to log into websites that support both login methods. As soon as the user taps on the username input field, an autofill suggestion dialog pops up with the stored passkeys and password autofill suggestions. The user can then choose an account and use the device screen lock to sign in.

Moving image showing continual UI experience on the web

Note: To learn how to add passkeys support with conditional UI to your web app check out Create a passkey for passwordless logins and Sign in with a passkey through form autofill.

On Android, they used the Credential Manager API which supports multiple sign-in methods, such as username and password, passkeys, and federated sign-in solutions (such as Sign-in with Google) in a single API. The Credential Manager simplifies the development process and it has enabled Dashlane to implement passkeys support on Android in 8 weeks with a team of one engineer.

Moving image showing authentication UI experience in android

Note: If you are a credential provider, such as a password manager app, check out the guide on how to integrate Credential Manager with your credential provider solution.


Data shows that users are more satisfied with the passkey flows than the existing password flows.

The conversion rate is 92% on passkey authentication opportunities on the web (when Dashlane suggests a saved passkey for the user to sign in), compared to a 54% conversion rate on opportunities to automatically sign in with passwords. That’s a 70% increase in conversion rate compared to passwords–a great sign for passkey adoption.

Graph showing evolution of positive actions on passkeys, measuring the rates of authentication with a passkey and registration of a passkey over a six month period

Image showing password sign-in prompt
Password sign-in prompt.

Image showing passkey sign-in prompt
Passkey sign-in prompt.

The conversion rate here refers to user actions when they visit websites that support passkeys. If a user attempts to register or use a passkey they will see a Dashlane dialog appear on Chrome on desktop. If they proceed and create new or use an existing passkey it is considered a success. If they dismiss the dialog or cancel passkey creation, it’s considered a failure. The same user experience flow applies to passwords.

Dashlane also saw a 63% conversion rate on passkey registration opportunities (when Dashlane offers to save a newly created passkey to the user’s vault) compared to only around 25% conversion rate on suggestions to save new passwords. This indicates that Dashlane’s suggestions to save passkeys are more relevant and precise than the suggestions to save passwords.

Image showing save passkey prompt
Save passkey prompt.

Image showing save password prompt
Save password prompt.

Dashlane observed an acceleration of passkey usage with 6.8% average weekly growth of passkeys saved and used on the web.

graph showing % of Active users that performed a passkey related event, out of users having ever interacted with a passkey with a moving average on 7 days over a six month period
Save password prompt.


While passkeys are a new technology that users are just starting to get familiar with, the adoption rate and positive engagement rates show that Dashlane users are more satisfied with passkey flows than the existing password flows. 

“Staying up to date on developments in the market landscape and industry, anticipating the potential impact to your customers’ experience, and being ready to meet their needs can pay off. Thanks in part to our rapid implementation of the Credential Manager API, customers can rest assured that they can continue to rely on Dashlane to store and help them access services, no matter how authentication methods evolve.“ –Rew Islam, Director of Product Engineering and Innovation at Dashlane

Dashlane tracks and investigates all passkey errors and says that there haven’t been many. They also receive few questions from customers around how to use or manage their passkeys. This can be a sign of an intuitive user experience, clear help center documentation, a tendency of passkey users today already being knowledgeable about passkeys, or some combination of these factors.

MediaPipe On-Device Text-to-Image Generation Solution Now Available for Android Developers

Posted by Paul Ruiz – Senior Developer Relations Engineer, and Kris Tonthat – Technical Writer

Earlier this year, we previewed on-device text-to-image generation with diffusion models for Android via MediaPipe Solutions. Today we’re happy to announce that this is available as an early, experimental solution, Image Generator, for developers to try out on Android devices, allowing you to easily generate images entirely on-device in as quickly as ~15 seconds on higher end devices. We can’t wait to see what you create!

There are three primary ways that you can use the new MediaPipe Image Generator task:

  1. Text-to-image generation based on text prompts using standard diffusion models.
  2. Controllable text-to-image generation based on text prompts and conditioning images using diffusion plugins.
  3. Customized text-to-image generation based on text prompts using Low-Rank Adaptation (LoRA) weights that allow you to create images of specific concepts that you pre-define for your unique use-cases.


Before we get into all of the fun and exciting parts of this new MediaPipe task, it’s important to know that our Image Generation API supports any models that exactly match the Stable Diffusion v1.5 architecture. You can use a pretrained model or your fine-tuned models by converting it to a model format supported by MediaPipe Image Generator using our conversion script.

You can also customize a foundation model via MediaPipe Diffusion LoRA fine-tuning on Vertex AI, injecting new concepts into a foundation model without having to fine-tune the whole model. You can find more information about this process in our official documentation.

If you want to try this task out today without any customization, we also provide links to a few verified working models in that same documentation.

Image Generation through Diffusion Models

The most straightforward way to try the Image Generator task is to give it a text prompt, and then receive a result image using a diffusion model.

Like MediaPipe’s other tasks, you will start by creating an options object. In this case you will only need to define the path to your foundation model files on the device. Once you have that options object, you can create the ImageGenerator.

val options = ImageGeneratorOptions.builder().setImageGeneratorModelDirectory(MODEL_PATH).build() imageGenerator = ImageGenerator.createFromOptions(context, options)

After creating your new ImageGenerator, you can create a new image by passing in the prompt, the number of iterations the generator should go through for generating, and a seed value. This will run a blocking operation to create a new image, so you will want to run it in a background thread before returning your new Bitmap result object.

val result = imageGenerator.generate(prompt_string, iterations, seed) val bitmap = BitmapExtractor.extract(result?.generatedImage())

In addition to this simple input in/result out format, we also support a way for you to step through each iteration manually through the execute() function, receiving the intermediate result images back at different stages to show the generative progress. While getting intermediate results back isn’t recommended for most apps due to performance and complexity, it is a nice way to demonstrate what’s happening under the hood. This is a little more of an in-depth process, but you can find this demo, as well as the other examples shown in this post, in our official example app on GitHub.

Moving image of an image generating in MediaPipe from the following prompt: a colorful cartoon racoon wearing a floppy wide brimmed hat holding a stick walking through the forest, animated, three-quarter view, painting

Image Generation with Plugins

While being able to create new images from only a prompt on a device is already a huge step, we’ve taken it a little further by implementing a new plugin system which enables the diffusion model to accept a condition image along with a text prompt as its inputs.

We currently support three different ways that you can provide a foundation for your generations: facial structures, edge detection, and depth awareness. The plugins give you the ability to provide an image, extract specific structures from it, and then create new images using those structures.

Moving image of an image generating in MediaPipe from a provided image of a beige toy car, plus the following prompt: cool green race car

LoRA Weights

The third major feature we’re rolling out today is the ability to customize the Image Generator task with LoRA to teach a foundation model about a new concept, such as specific objects, people, or styles presented during training. With the new LoRA weights, the Image Generator becomes a specialized generator that is able to inject specific concepts into generated images.

LoRA weights are useful for cases where you may want every image to be in the style of an oil painting, or a particular teapot to appear in any created setting. You can find more information about LoRA weights on Vertex AI in the MediaPipe Stable Diffusion LoRA model card, and create them using this notebook. Once generated, you can deploy the LoRA weights on-device using the MediaPipe Tasks Image Generator API, or for optimized server inference through Vertex AI’s one-click deployment.

In the example below, we created LoRA weights using several images of a teapot from the Dreambooth teapot training image set. Then we use the weights to generate a new image of the teapot in different settings.

A grid of four photos of teapots generated with training prompt 'a photo of a monadikos teapot'on the left, and a moving image showing an image being generated in MediaPipe from the propmt 'a bright purple monadikos teapot sitting in top of a green table with orange teacups'
Image generation with the LoRA weights

Next Steps

This is just the beginning of what we plan to support with on-device image generation. We’re looking forward to seeing all of the great things the developer community builds, so be sure to post them on X (formally Twitter) with the hashtag #MediaPipeImageGen and tag @GoogleDevs. You can check out the official sample on GitHub demonstrating everything you’ve just learned about, read through our official documentation for even more details, and keep an eye on the Google for Developers YouTube channel for updates and tutorials as they’re released by the MediaPipe team.


We’d like to thank all team members who contributed to this work: Lu Wang, Yi-Chun Kuo, Sebastian Schmidt, Kris Tonthat, Jiuqiang Tang, Khanh LeViet, Paul Ruiz, Qifei Wang, Yang Zhao, Yuqi Li, Lawrence Chan, Tingbo Hou, Joe Zou, Raman Sarokin, Juhyun Lee, Geng Yan, Ekaterina Ignasheva, Shanthal Vasanth, Glenn Cameron, Mark Sherwood, Andrei Kulik, Chuo-Ling Chang, and Matthias Grundmann from the Core ML team, as well as Changyu Zhu, Genquan Duan, Bo Wu, Ting Yu, and Shengyang Dai from Google Cloud.

Improving user safety in OAuth flows through new OAuth Custom URI scheme restrictions

Posted by Vikrant Rana, Product Manager

OAuth 2.0 Custom URI schemes are known to be vulnerable to app impersonation attacks. As part of Google’s continuous commitment to user safety and finding ways to make it safer to use third-party applications that access Google user data, we will be restricting the use of custom URI scheme methods. They’ll be disallowed for new Chrome extensions and will no longer be supported for Android apps by default.

Disallowing Custom URI scheme redirect method for new Chrome Extensions

To protect users from malicious actors who might impersonate Chrome extensions and steal their credentials, we no longer allow new extensions to use OAuth custom URI scheme methods. Instead, implement OAuth using Chrome Identity API, a more secure way to deliver OAuth 2.0 response to your app.

What do developers need to do?

New Chrome extensions will be required to use the Chrome Identity API method for authorization. While existing OAuth client configurations are not affected by this change, we strongly encourage you to migrate them to the Chrome Identity API method. In the future, we may disallow Custom URI scheme methods and require all extensions to use the Chrome Identity API method.

Disabling Custom URI scheme redirect method for Android clients by default

By default, new Android apps will no longer be allowed to use Custom URI schemes to make authorization requests. Instead, consider using Google Identity Services for Android SDK to deliver the OAuth 2.0 response directly to your app.

What do developers need to do?

We strongly recommend switching existing apps to use the Google Identity Services for Android SDK. If you're creating a new app and the recommended alternative doesn’t work for your needs, you can enable the Custom URI scheme method for your app in the “Advanced Settings” section of the client configuration page on the Google API Console.

User-facing error message

Users may see an “invalid request” error message if they try to use an app that is making unauthorized requests using the Custom URI scheme method. They can learn more about this error by clicking on the "Learn more" link in the error message.

Image of user facing error message
User-facing error example

Developer-facing error message

Developers will be able to see additional error information when testing user flows for their applications. They can get more information about the error by clicking on the “see error details” link, including its root cause and links to instructions on how to resolve the error.

Image of developer facing error message
Developer-facing error example

Related content

Grow user acquisition and store conversions with the updated Play Store Listing Certificate course

Posted by Rob Simpson, Product Manager - Google Play & Joe Davis, Manager - Google Play Academy

Since we launched the Google Play Store Listing Certificate in 2021, our no-cost, self-paced training courses have helped thousands of developers in over 80 countries increase their app installs. Over the course of the training, developers learn essential mobile marketing best practices, including how to leverage Play Console growth tools like store listing experiments (SLEs) and custom store listings (CSLs).

Today, we’re excited to release a major update to our self-paced training, covering all the latest CSL and SLE features, as well as real-world examples showing how you might use them to drive user growth. We’re also releasing video study guide series to help you lock in your new knowledge ahead of the exam.

Built for app growth marketers, take Google Play Academy's online growth marketing training and get certified in Google Play Store Listing best practices!
Videos: Google Play Store Listing Certificate Study Guide

What’s new: New features in custom store listings and store listing experiments

The new course content focuses on custom store listings and store listing experiments. For the unfamiliar, custom store listings allow you to show different versions of your title’s Play Store content to different people. For example, you might create versions tailored to users in different countries where feature availability varies, or an experience just for users who have lapsed or churned.

Custom store listings can help you convey the most effective messaging for different users. Based on an internal comparative analysis, CSLs can help increase an app or game’s monthly active users (MAUs) by an average of over 3.5%1.

Store listing experiments, on the other hand, offer a way to explore what icons, descriptions, screenshots (and more) convert the best for your title on the Play Store.

These are features you can use today! Google Play Academy’s training now includes four new courses on custom store listings, four updated existing courses, and nine new study guide videos.

Finding app and career growth

Here’s what some developers, entrepreneurs and marketers have said about their experience after getting trained up and certified:

Learning best practices for store listing experiments allowed me to know more about our audience. Something that simple as using the proper icon increased acquisitions of one of our games by approximately 60%

Adrian Mojica 
Marketing Creative, GameHouse (Spain) 

The knowledge I gained empowered me to make more informed decisions and learn effective strategies. The credibility I got from the certificate has opened new doors in my career. 

Roshni Kumari 
Student & Campus Ambassador (India)

Play Academy increased efficiency in mentoring relationships by 50%, and we've seen a 30% increase in our game launch speed overall. 

Kimmie Vu
Chief Marketing Officer, Rocket Game Studio (Vietnam)

Top tips to prepare for your certificate exam

  1. Take the training and watch the study guide videos
  2. Take the online training on Google Play Academy to learn best practices to help create a winning store listing, then lock in your knowledge with the new video study guides. You’ll learn key skills to help you drive growth with high-quality and policy-compliant store listings.

  3. Pass the exam and get certified
  4. After the training, take the exam to get an industry-recognized certificate. You will also be invited to join Google Developer Certification Directory, where you can network with other Google-certified developers.

  5. Get started with custom store listings and experiments
  6. Time to put your new skills into action. Start with 2-3 custom store listings for markets important to your app or game, such as users in certain countries or lapsed or churned users. Or test a new icon or short description.

Start your learning journey on Google Play Academy today!

1 Source: Internal Google data [Nov 2022] comparing titles that use CSL to those that do not.

Try the K2 compiler in your Android projects

Posted by Márton Braun, Developer Relations Engineer

The Kotlin compiler is being rewritten for Kotlin 2.0. The new compiler implementation–codenamed K2–brings with it significant build speed improvements, compiling Kotlin code up to twice as fast as the original compiler. It also has a more flexible architecture that will enable the introduction of new language features after 2.0.

Try the new compiler

With Kotlin 1.9, K2 is now available in Beta for JVM targets, including Android projects. To help stabilize the new compiler and make sure you’re ready for Kotlin 2.0, we encourage you to try compiling your projects with the new compiler. If you run into any issues, you can report them on the Kotlin issue tracker.

To try the new compiler, update to Kotlin 1.9 and add the following to your project’s gradle.properties file:

Note that the new compiler should not be used for production builds yet. A good approach for trying it early is to create a separate branch in your project for compiling with K2. You can find an example of this in the Now in Android repository.

Tooling support

Plugins and tools that depend on the Kotlin compiler frontend will also have to be updated to add support for K2. Some tools already have experimental support for building with K2: the Jetpack Compose compiler plugin supports K2 starting in 1.5.0, which is compatible with Kotlin 1.9.

Android Lint also supports K2 starting in version 8.2.0-alpha12. To run Lint on K2, upgrade to this version and add android.lint.useK2Uast=true to your gradle.properties file. Note that any custom lint rules that rely on APIs from the old frontend will have to be updated to use the analysis API instead.

Adding K2 support in other tools is still in progress: KSP and KAPT tasks currently fall back to using the old compiler when building your project with K2. However, compilation tasks can still run using K2 when these tools are used.

Android Studio also relies on the Kotlin compiler for code analysis. Until Android Studio has support for K2, building with K2 might result in some discrepancies between the code analysis of the IDE and command line builds in certain edge cases.

If you use any additional compiler plugins, check their documentation to see whether they are compatible with K2 yet.

Get started with the K2 compiler today

The Kotlin 2.0 Compiler offers significant improvements to help you ship updates faster, be more productive, and spend more time focusing on what makes your app unique.

It already works with Jetpack Compose and we have a roadmap to improve support in other tools, including Android Studio, KSP, and compiler plugins. Now is a great time to try it in your app's codebase and provide feedback related to Kotlin, Compose, or Lint.

Credential Manager beta: easy & secure authentication with passkeys on Android

Posted by Diego Zavala, Product Manager, and Niharika Arora, Android Developer Relations Engineer

Today, we are excited to announce the beta release of Credential Manager with a finalized API surface, making it suitable for use in production. As we previously announced, Credential Manager is a new Jetpack library that allows app developers to simplify their users' authentication journey, while also increasing security with support of passkeys.

Authentication provides secure access to personalized experiences, but it has challenges. Passwords, which are widely used today, are difficult to use, remember and are not always secure. Many applications and services require two-factor authentication (2FA) to login, adding more friction to the user's flow. Lastly, sign-in methods have proliferated, making it difficult for users to remember how they signed in. This proliferation has also added complexity for developers, who now need to support multiple integrations and APIs.

Credential Manager brings support for passkeys, a new passwordless authentication mechanism, together with traditional sign-in methods, such as passwords and federated sign-in, into a single interface for the user and a unified API for developers.

image showing end-to-end journey to sign in using a passkey on a mobile device
End-to-end journey to sign in using a passkey

With Credential Manager, users will benefit from seeing all their credentials in one place; passkeys, passwords and federated credentials (such as Sign in with Google), without needing to tap three different places. This reduces user confusion and simplifies choices when logging in.

image showing the unified account selector that support multiple credential types across multiple accounts on a mobile device
Unified account selector that support multiple credential types across multiple accounts

Credential Manager also makes the login experience simpler by deduping across sign-in methods for the same account and surfacing only the safest and simplest authentication method, further reducing the number of choices users need to make. So, if a user has a password and a passkey for a single account, they won’t need to decide between them when signing in; rather, the system will propose using the passkey - the safest and simplest option. That way, users can focus on choosing the right account instead of the underlying technology.

image showing how a passkey and a password for the same account are deduped on a mobile device
A passkey and a password for the same account are deduped

For developers, Credential Manager supports multiple sign-in mechanisms within a single API. It provides support for passkeys on Android apps, enabling the transition to a passwordless future. And at the same time, it also supports passwords and federated sign in like Sign in With Google, simplifying integration requirements and ongoing maintenance.

Who is already using Credential Manager?

Kayak has already integrated with Credential Manager, providing users with the advantages of passkeys and simpler authentication flows.

"Passkeys make creating an account lightning fast by removing the need for password creation or navigating to a separate app to get a link or code. As a bonus, implementing the new Credential Manager library also reduced technical debt in our code base by putting passkeys, passwords and Google sign-in all into one new modern UI. Indeed, users are able to sign up to Kayak with passkeys twice as fast as with an email link, which also improves the sign-in completion rate."  

– Matthias Keller, Chief Scientist and SVP, Technology at Kayak 

Something similar is observed on Shopify

“Passkeys work across browsers and our mobile app, so it was a no-brainer decision for our team to implement, and the resulting one-tap user experience has been truly magical. Buyers who are using passkeys to log in to Shop are doing so 14% faster than those who are using other login methods (such as email or SMS verification)”

– Mathieu Perreault, Director of Engineering at Shopify

Support for multiple password managers

Credential Manager on Android 14 and higher supports multiple password managers at the same time, enabling users to choose the provider of their choice to store, sync and manage their credentials. We are excited to be working with several leading providers like Dashlane on their integration with Credential Manager.

“Adopting passkeys was a no-brainer for us. It simplifies sign-ins, replaces the guesswork of traditional authentication methods with a reliable standard, and helps our users ditch the downsides of passwords. Simply put, it’s a big win for both us and our users. Dashlane is ready to serve passkeys on Android 14!”

– Rew Islam, Director of Product Engineering and Innovation at Dashlane

Get started

To start using Credential Manager, you can refer to our integration guide.

We'd love to hear your input during this beta release, so please let us know about your experience integrating with Credential Manager, using passkeys, or any other feedback you might have: