In Android, we're always looking for ways to improve the user and developer experience by making those experiences as stable as possible. In this spirit, we've been working to ensure that apps don't use non-SDK interfaces, since doing so risks crashes for users and emergency rollouts for developers. In Android N, we restricted the set of symbols that C/C++ code could use. This change ensured that apps using C++ rely on stable NDK interfaces rather than incur the incremental crashes caused by reliance on unstable, non-NDK interfaces. Starting in the next release of Android, we will further increase stability by expanding these restrictions to cover the Java language interfaces of the SDK.
Starting in the next release of Android, some non-SDK methods and fields will be restricted so that you cannot access them -- either directly, via reflection, or JNI. If you try, you can see errors such as NoSuchFieldException or NoSuchMethodException.
Initially, this restriction will impact interfaces with low or no usage. It is an explicit goal of our planning and design to respect our developer community and create the absolute minimum of change while addressing app stability issues flagged by our users and device manufacturers. In cases where a migration to SDK methods will be possible but is likely to be technically challenging, we'll allow continued usage until your app is updated to target the latest API level. We plan to broaden these restrictions in future platform versions, giving developers time to migrate with long advance warning, and also giving us time to gather feedback about any needed SDK interfaces. We have always said that using non-SDK interfaces is always risky -- they might change in any release as we refactor code to add features or fix bugs. So if your app currently relies on non-SDK interfaces, you should begin planning a migration to SDK alternatives.
Because the Java language has different features from C++, this restriction will take a slightly different form than the previous symbol restriction. You should not access classes that are not part of our SDK, but you also need to be sure that you are only using the officially documented parts of each class. In particular, this means that you should not plan to access methods or fields that are not listed in the SDK when you interact with a class via semantics such as reflection.
We know that some apps may be using non-SDK interfaces in ways that do not have an SDK alternative. We value your feedback about where and how we need to expand and improve the public APIs for you. If you feel that you'll need the SDK API expanded before you can stop using non-SDK ones, please tell us via our bug tracker. We will be monitoring this list closely and using this valuable feedback to prioritize. It is critical for us to get this feedback in a timely manner so that we can continue to both tune the blacklist to minimize developer impact and also begin developing any needed alternatives for future platforms.
In the next Android developer preview, you'll be able to run your existing apps and see warnings when you use a non-SDK interface that will be subject to blacklist or greylist in the final release. It's always a best practice to make sure your app runs on the developer preview, but you should pay specific attention to the interface compatibility warnings if you are concerned that you may be impacted.
In conjunction with the next developer preview and the new bug tracker category, we'll be monitoring usage of non-SDK interfaces. In cases where official SDK alternatives already exist, we'll publish official guidance on how to migrate away from commonly used non-SDK interfaces.
Transitions in Material Design apps provide visual continuity. As the user navigates the app, views in the app change state. Motion and transformation reinforce the idea that interfaces are tangible, connecting common elements from one view to the next.
This post aims to provide guidelines and implementation for a specific continuous transition between Android Fragments. We will demonstrate how to implement a transition from an image in a RecyclerView into an image in a ViewPager and back, using 'Shared Elements' to determine which views participate in the transition and how. We will also handle the tricky case of transitioning back to the grid after paging to an item that was previously offscreen.
This is the result we are aiming for:
If you wish to skip the explanation and go straight to the code, you can find it here.
A shared element transition determines how views that are present in two fragments transition between them. For example, an image that is displayed on an ImageView on both Fragment A and Fragment B transitions from A to B when B becomes visible.
ImageView
A
B
There are numerous previously published examples which explain how shared elements work and how to implement a basic Fragment transition. This post will skip most of the basics and will walk through the specifics on how to create a working transition into a ViewPager and back. However, if you'd like to learn more about transitions, I recommend starting by reading about transitions at the Android's developers website, and take the time to watch this 2016 Google I/O presentation.
We would like to support a seamless back and forth transition. This includes a transition from the grid to the pager, and then a transition back to the relevant image, even when the user paged to a different image.
To do so, we will need to find a way to dynamically remap the shared elements in order to provide the Android's transition system what it needs to do its magic!
Shared element transitions are powerful, but can be tricky when dealing with elements that need to be loaded before we can transition to them. The transition may simply not work as expected when views at the target fragment are not laid out and ready.
In this project, there are two areas where a loading time affects the shared element transition:
ViewPager
RecyclerView
Before we dive into the juicy transitions, here is a little bit about how the demo app is structured.
The MainActivity loads a GridFragment to present a RecyclerView of images. The RecyclerView adapter loads the image items (a constant array that is defined at the ImageData class), and manages the onClick events by replacing the displayed GridFragment with an ImagePagerFragment.
GridFragment
ImageData
onClick
ImagePagerFragment
The ImagePagerFragment adapter loads the nested ImageFragments to display the individual images when paging happens.
ImageFragments
Note: The demo app implementation uses Glide, which loads images into views asynchronously. The images in the demo app are bundled with it. However, you may easily convert the ImageData class to hold URL strings that point to online images.
To communicate the selected image position between the fragments, we will use the MainActivity as a place to store the position.
MainActivity
When an item is clicked, or when a page is changed, the MainActivity is updated with the relevant item's position.
The stored position is later used in several places:
As mentioned above, we will need to find a way to dynamically remap the shared elements in order to give the transition system what it needs to do its magic.
Using a static mapping by setting up transitionName attributes for the image views at the XML will not work, as we are dealing with an arbitrary amount of views that share the same layout (e.g. views inflated by the RecyclerView adapter, or views inflated by the ImageFragment).
transitionName
ImageFragment
To accomplish this, we'll use some of what the transition system provides us:
setTransitionName
onCreateView
SharedElementCallbacks
onMapSharedElements
The first thing we set up to initiate a transition for a fragment replacement is at the FragmentManager transaction preparation. We need to inform the system that we have a shared element transition.
FragmentManager
fragment.getFragmentManager() .beginTransaction() .setReorderingAllowed(true) // setAllowOptimization before 26.1.0 .addSharedElement(imageView, imageView.getTransitionName()) .replace(R.id.fragment_container, new ImagePagerFragment(), ImagePagerFragment.class.getSimpleName()) .addToBackStack(null) .commit();
The setReorderingAllowed is set to true. It will reorder the state changes of fragments to allow for better shared element transitions. Added fragments will have onCreate(Bundle) called before replaced fragments have onDestroy() called, allowing the shared view to get created and laid out before the transition starts.
setReorderingAllowed
true
onCreate(Bundle)
onDestroy()
To define how the image transitions when it animates to its new location, we set up a TransitionSet in an XML file and load it at the ImagePagerFragment.
TransitionSet
<ImagePagerFragment.java>
Transition transition = TransitionInflater.from(getContext()) .inflateTransition(R.transition.image_shared_element_transition); setSharedElementEnterTransition(transition);
<image_shared_element_transition.xml>
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="375" android:interpolator="@android:interpolator/fast_out_slow_in" android:transitionOrdering="together"> <changeClipBounds/> <changeTransform/> <changeBounds/> </transitionSet>
We'll start by adjusting the shared element mapping when leaving the GridFragment. For that, we will call the setExitSharedElementCallback() and provide it with a SharedElementCallback which will map the element names to the views we'd like to include in the transition.
setExitSharedElementCallback()
SharedElementCallback
It's important to note that this callback will be called while exiting the Fragment when the fragment-transaction occurs, and while re-entering the Fragment when it's popped out of the backstack (on back navigation). We will use this behavior to remap the shared view and adjust the transition to handle cases where the view is changed after paging the images.
Fragment
In this specific case, we are only interested in a single ImageView transition from the grid to the fragment the view-pager holds, so the mapping only needs to be adjusted for the first named element received at the onMapSharedElements callback.
<GridFragment.java>
setExitSharedElementCallback( new SharedElementCallback() { @Override public void onMapSharedElements( List<String> names, Map<String, View> sharedElements) { // Locate the ViewHolder for the clicked position. RecyclerView.ViewHolder selectedViewHolder = recyclerView .findViewHolderForAdapterPosition(MainActivity.currentPosition); if (selectedViewHolder == null || selectedViewHolder.itemView == null) { return; } // Map the first shared element name to the child ImageView. sharedElements .put(names.get(0), selectedViewHolder.itemView.findViewById(R.id.card_image)); } });
We also need to adjust the shared element mapping when entering the ImagePagerFragment. For that, we will call the setEnterSharedElementCallback().
setEnterSharedElementCallback()
setEnterSharedElementCallback( new SharedElementCallback() { @Override public void onMapSharedElements( List<String> names, Map<String, View> sharedElements) { // Locate the image view at the primary fragment (the ImageFragment // that is currently visible). To locate the fragment, call // instantiateItem with the selection position. // At this stage, the method will simply return the fragment at the // position and will not create a new one. Fragment currentFragment = (Fragment) viewPager.getAdapter() .instantiateItem(viewPager, MainActivity.currentPosition); View view = currentFragment.getView(); if (view == null) { return; } // Map the first shared element name to the child ImageView. sharedElements.put(names.get(0), view.findViewById(R.id.image)); } });
The images we would like to transition are loaded into the grid and the pager and take time to load. To make it work properly, we will need to postpone the transition until the participating views are ready (e.g. laid out and loaded with the image data).
To do so, we call a postponeEnterTransition() in our fragments' onCreateView(), and once the image is loaded, we start the transition by calling startPostponedEnterTransition().
postponeEnterTransition()
onCreateView()
startPostponedEnterTransition()
Note: postpone is called for both the grid and the pager fragments to support both forward and backward transitions when navigating the app.
Since we are using Glide to load the images, we set up listeners that trigger the enter transition when images are loaded.
This is done in two places:
Here is how the ImageFragment loads an image and notifies its parent when it's ready.
Note that the postponeEnterTransition is made at the the ImagePagerFragment, while the startPostponeEnterTransition is called from the child ImageFragment that is created by the pager.
postponeEnterTransition
startPostponeEnterTransition
<ImageFragment.java>
Glide.with(this) .load(arguments.getInt(KEY_IMAGE_RES)) // Load the image resource .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { getParentFragment().startPostponedEnterTransition(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { getParentFragment().startPostponedEnterTransition(); return false; } }) .into((ImageView) view.findViewById(R.id.image));
As you may have noticed, we also call to start the postponed transition when the loading fails. This is important to prevent the UI from hanging during failure.
To make our transitions even smoother, we would like to fade out the grid items when the image transitions to the pager view.
To do that, we create a TransitionSet that is applied as an exit transition for the GridFragment.
setExitTransition(TransitionInflater.from(getContext()) .inflateTransition(R.transition.grid_exit_transition));
<grid_exit_transition.xml>
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="375" android:interpolator="@android:interpolator/fast_out_slow_in" android:startDelay="25"> <fade> <targets android:targetId="@id/card_view"/> </fade> </transitionSet>
This is what the transition looks like after this exit transition is set up:
As you may have noticed, the transition is still not completely polished with this setup. The fade animation is running for all the grid's card views, including the card that holds the image that transitions to the pager.
To fix it, we exclude the clicked card from the exit transition before commiting the fragment transaction at the GridAdapter.
GridAdapter
// The 'view' is the card view that was clicked to initiate the transition. ((TransitionSet) fragment.getExitTransition()).excludeTarget(view, true);
After this change, the animation looks much better (the clicked card doesn't fade out as part of the exit transition, while the rest of the cards fade out):
As a final touch, we set up the GridFragment to scroll and reveal the card we transition to when navigating back from the pager (done at the onViewCreated):
onViewCreated
recyclerView.addOnLayoutChangeListener( new OnLayoutChangeListener() { @Override public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { recyclerView.removeOnLayoutChangeListener(this); final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); View viewAtPosition = layoutManager.findViewByPosition(MainActivity.currentPosition); // Scroll to position if the view for the current position is null (not // currently part of layout manager children), or it's not completely // visible. if (viewAtPosition == null || layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){ recyclerView.post(() -> layoutManager.scrollToPosition(MainActivity.currentPosition)); } } });
Wrapping up
In this article, we implemented a smooth transition from a RecyclerView to a ViewPager and back.
We showed how to postpone a transition and start it after the views are ready. We also implemented shared element remapping to get the transition going when shared views are changing dynamically while navigating the app.
These changes transformed our app's fragment transitions to provide better visual continuity as users interact with it.
The code for the demo app can be found here.
Promotions can be a valuable tool to increase user engagement or attract new users by offering content or features to a limited number of users free of charge.
We are happy to share an improvement in the Google Play Developer API that makes it easier to track your promotions from your own backend. Starting today, the API for Purchases.products will return "Promo" as a new value for the field purchaseType when the user redeems a promo code. Now, the possible values are:
Promo"
purchaseType
0. Test
1. Promo
For purchases made using the standard in-app billing flow, the field will continue to not be set in the API response.
Please note: This state is only returned by the Purchases.products API. For subscriptions you may use Free Trials to offer free of charge subscription periods.
For more details about how to create and redeem promo codes, check the In-app Promotions documentation. For more details about the server-side API, check the Google Play Developer API documentation.
We have just wrapped up the second edition of the Google Play Indie Games Contest in Europe! The iconic Saatchi Gallery in London welcomed 20 developers, from 12 countries, who showcased their games to the audience of gamers, industry experts, and journalists.
The finalists' games were on show to the public, who spent three hours trying out their games and voting for their favourites, alongside the Google Play team. The top 10 finalists were then selected, and went on to pitch their games, and compete for the big prizes in front of our jury.
Please join us in congratulating the winners! They will be bringing home a well-deserved diploma, along with a prize package that will help them reach more gamers worldwide; including premium placement on the Google Play Store, marketing campaigns of up to 100,000 EUR and influencer campaigns of up to 50,000 EUR, the latest Google hardware, tickets to Google I/O, and much more.
It's really inspiring to see the excitement around this second edition, and great to see the new wave of indie games coming from Europe. We are already looking forward to playing the games that will be developed in 2018!
Check out the main winners and the other finalists on the Google Play Store!
Bury me, my love
Playdius
France
A reality-inspired interactive fiction designed for mobile phones. It tells the story of Nour, a Syrian woman trying to reach Europe in hope of a better life.
Old Man's Journey
Broken Rules Interactive Media GmbH
Austria
A story game about life's precious moments, broken dreams, and changed plans.
Yellow
Bart Bonte
Belgium
A puzzle game for you! A love letter to a marvelous colour and to the little wonder called touchscreens. Warning: very yellow!
Captain Tom Galactic Traveler
Picodongames
An open world platformer and space exploration game. Embark on an exploratory mission, discover planets, collect oxygen, play with gravity.
I Love Hue
Zut!
United Kingdom
A minimalist, ambient puzzle game influenced by mindfulness apps and abstract art. Players arrange shuffled mosaics of coloured tiles into perfectly ordered palettes.
Jodeo
Gamebra.in
Turkey
Jodeo is a 2D jelly critter. There's something it's curious about: what if 3D objects and 2D physics are in the same game? How can 2D objects interact with 3D objects?
Kami 2
State of Play
The calming yet addictive puzzle game is back! With over 100 handcrafted puzzles, it takes you on a mind-twisting journey that combines logic and problem-solving.
Kenshō
FIFTYTWO
Russia
A tile sliding puzzle with a wonderful soundtrack. Mysterious things happen in a ruined room. Doors inside that room lead to different worlds and beautiful landscapes.
No More Buttons
Tommy Søreide Kjær
Norway
A hand-drawn platformer where the buttons are part of the environment.
The Big Journey
Catfishbox
Ukraine
Designed for kids and adults alike, this a beautiful, casual adventure. Tilt to roll around and explore a beautiful world with Mr. Whiskers.
How useful did you find this blogpost?
★ ★ ★ ★ ★
What happens to app usage and accessibility when people get new phones? The feedback we've had is that people want apps to work straight out of the box, just like on their old phones.
Developers of successful apps might also be used to thinking about user activation in a model borrowed straight from web. On the web, people register new accounts, activate by finding great features, then become retained when they experience value, and come back repeatedly to use your web page.
The story is much the same on mobile. People register to create new accounts, activate by using your great features, then become retained when they find value and repeatedly launch your app. However, there's one big difference. Android apps typically store more information compared to your average web session. You usually never have to re-enter your password for an Android app for years, post account creation, that is until the moment you get a new phone.
Getting a new phone can be a rare event for many people - some going years between upgrading devices. However, overall a large proportion of those who use your app will get a new phone every year. We have several tools to help you keep people logged in, engaged, and happy when they use your app on a new phone.
Auto Backup for apps should be configured for every application. This feature does exactly what it says - automatically backs up your app data. So when people get a new phone, their app data is automatically restored before your app launches.
To configure Auto Backup for your app you need to setup include/exclude rules:
AndroidManifest.xml
<application ... android:fullBackupContent="@xml/autobackup">
xml/autobackup.xml
<?xml version="1.0" encoding="utf-8"?> <full-backup-content> <include domain="sharedpref" path="."/> <exclude domain="sharedpref" path="device.xml"/> </full-backup-content>
When configuring include/exclude rules it's important to avoid storing sensitive user data in Auto Backup, although it's a great place to store user specific settings and other app content!
To implement tracking for Auto Backup register a BackupAgent and listen for onQuotaExceeded(long, long) callback. If your app exceeds the 25MB backup limit, this callback will be your notification of failure. In a well configured app this will never happen, so you can track it as a crash report.
Learn more about Auto Backup for apps.
When we talk to people about the experiences they want on their new phones they're very clear; they want your app to remember who they are, and they don't want to re-enter a password. There are several ways you can accomplish this as a developer:
<TextView android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autofillHints="username" /> <TextView android:id="@+id/password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autofillHints="password" /> <TextView android:id="@+id/captcha" android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAutofill="no" />
android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autofillHints="username" />
If you haven't already, try the Auto Backup for Android Codelab, and SmartLock Codelab.
Improving retention on Android for many people will involve trying to overcome the friction of device switches. With a rich toolbox at your disposal to transfer settings with Auto Backup, and to improve the login experience with Google Sign-In, Smart Lock for Passwords, Autofill, and Account Transfer API, you have the opportunity to deliver a great user story: your app works on people's new phones, just like it did on their old phones.
Today, we are announcing the preview of Android KTX - a set of extensions designed to make writing Kotlin code for Android more concise, idiomatic, and pleasant. Android KTX provides a nice API layer on top of both Android framework and Support Library to make writing your Kotlin code more natural.
The portion of Android KTX that covers the Android framework is now available in our GitHub repo. We invite you to try it out to give us your feedback and contributions. The other parts of Android KTX that cover the Android Support Library will be available in upcoming Support Library releases.
Let's take a look at some examples of how Android KTX can help you write more natural and concise Kotlin code.
Code Samples Using Android KTX
String to Uri
Let's start with this simple example. Normally, you'd call Uri.parse(uriString). Android KTX adds an extension function to the String class that allows you to convert strings to URIs more naturally.
Uri.parse(uriString)
val uri = Uri.parse(myUriString)
val uri = myUriString.toUri()
Edit SharedPreferences
Editing SharedPreferences is a very common use case. The code using Android KTX is slightly shorter and more natural to read and write.
sharedPreferences.edit() .putBoolean(key, value) .apply()
sharedPreferences.edit { putBoolean(key, value) }
Translating path difference
In the code below, we translate the difference between two paths by 100px.
val pathDifference = Path(myPath1).apply { op(myPath2, Path.Op.DIFFERENCE) } val myPaint = Paint() canvas.apply { val checkpoint = save() translate(0F, 100F) drawPath(pathDifference, myPaint) restoreToCount(checkpoint) }
val pathDifference = myPath1 - myPath2 canvas.withTranslation(y = 100F) { drawPath(pathDifference, myPaint) }
Action on View onPreDraw
This example triggers an action with a View's onPreDraw callback. Without Android KTX, there is quite a bit of code you need to write.
view.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { viewTreeObserver.removeOnPreDrawListener(this) actionToBeTriggered() return true } })
view.doOnPreDraw { actionToBeTriggered() }
There are many more places where Android KTX can simplify your code. You can read the full API reference documentation on GitHub.
Getting Started
To start using Android KTX in your Android Kotlin projects, add the following to your app module's build.gradle file:
build.gradle
repositories { google() } dependencies { // Android KTX for framework API implementation 'androidx.core:core-ktx:0.1' ... }
Then, after you sync your project, the extensions appear automatically in the IDE's auto-complete list. Selecting an extension automatically adds the necessary import statement to your file.
Beware that the APIs are likely to change during the preview period. If you decide to use it in your projects, you should expect breaking changes before we reach the stable version.
You may notice that Android KTX uses package names that begin with androidx. This is a new package name prefix that we will be using in future versions of Android Support Library. We hope the division between android.* and androidx.* makes it more obvious which APIs are bundled with the platform, and which are static libraries for app developers that work across different versions of Android.
androidx
android.*
androidx.*
Today's preview launch is only the beginning. Over the next few months, we will iterate on the API as we incorporate your feedback and contributions. When the API has stabilized and we can commit to API compatibility, we plan to release Android KTX as part of the Android Support Library.
We look forward to building Android KTX together with you. Happy Kotlin-ing!
Deeplocal is a Pittsburgh-based innovation studio that makes inventions as marketing to help the world's most loved brands tell their stories. The team at Deeplocal built several fun and engaging robotics projects using Android Things. Leveraging the developer ecosystem surrounding the Android platform and the compute power of Android Things hardware, they were able to quickly and easily create robots powered by computer vision and machine learning.
DrawBot
DrawBot is a DIY drawing robot that transforms your selfies into physical works of art.
"The Android Things platform helped us move quickly from an idea, to prototype, to final product. Switching from phone apps to embedded code was easy in Android Studio, and we were able to pull in OpenCV modules, motor drivers, and other libraries as needed. The final version of our prototype was created two weeks after unboxing our first Android Things developer kit."
- Brian Bourgeois, Producer, Deeplocal
Want to build your own DrawBot? See the Hackster.io project for all the source code, schematics, and 3D models.
HandBot
A robotic hand that learns and reacts to hand gestures, HandBot visually recognizes gestures and applies machine learning.
"The Android Things platform made integration work for Handbot a breeze. Using TensorFlow, we were able to train a neural network to recognize hand gestures. Once this was created, we were able to use Android Things drivers to implement games in easy-to-read Android code. In a matter of weeks, we went from a fresh developer kit to competing against a robot hand in Rock, Paper, Scissors."
- Mike Derrick, Software Engineer, Deeplocal
Want to build your own HandBot? See the Hackster.io project for all the source code, schematics, and 3D models.
Visit the Google Hackster community to explore more inspiring ideas just like these, and join Google's IoT Developers Community on Google+ to get the latest platform updates, ask questions, and discuss ideas.