<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ian Lake on Medium]]></title>
        <description><![CDATA[Stories by Ian Lake on Medium]]></description>
        <link>https://medium.com/@ianhlake?source=rss-51a4f24f5367------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*kbRu4F5dUh57Bc6u.jpeg</url>
            <title>Stories by Ian Lake on Medium</title>
            <link>https://medium.com/@ianhlake?source=rss-51a4f24f5367------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 10 Oct 2026 20:56:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ianhlake/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Navigation Compose meet Type Safety]]></title>
            <link>https://medium.com/androiddevelopers/navigation-compose-meet-type-safety-e081fb3cf2f8?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/e081fb3cf2f8</guid>
            <category><![CDATA[androiddev]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 01 May 2024 23:09:46 GMT</pubDate>
            <atom:updated>2024-10-25T16:46:52.058Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TvycJaTB7Gj-7BNq4e-3iw.png" /></figure><h4>Bringing Safe Args to Navigation Compose</h4><p>As of <a href="https://developer.android.com/jetpack/androidx/releases/navigation#2.8.0-alpha08">Navigation 2.8.0-alpha08</a>, the Navigation Component has a full type safe system based on Kotlin Serialization for defining your navigation graph when using our Kotlin DSL, designed to work best with integrations like Navigation Compose.</p><h3>Kotlin DSL? What’s that for?</h3><p>The Navigation Component has three main components:</p><ul><li>Host — the UI element in your layout that displays the current ‘destination’</li><li>Graph — the data structure that defines all of the possible destinations in your app</li><li>Controller — the central coordinator that manages navigating between destinations and saving the back stack of destinations</li></ul><p>The Kotlin DSL is just one of the ways to build that navigation graph. Since <a href="https://developer.android.com/jetpack/androidx/releases/navigation#1.0.0-alpha01">the very first alpha</a> of Navigation back in 2018, Navigation has always offered three ways to build the graph:</p><ul><li>Manually constructing instances of NavGraph and adding destinations like Fragment Destinations to construct the graph (this is still the underlying base for everything else, but not something you should actively be doing yourself)</li><li>Inflating your graph from a navigation XML file, editing it by hand or via the <a href="https://developer.android.com/guide/navigation/design/editor">Navigation Editor</a></li><li>Using the Kotlin DSL to construct your navigation graph directly in your Kotlin code</li></ul><p>Navigation Compose was the first integration to really embrace the Kotlin DSL as <em>the</em> way to build your graph, purposefully moving to a more flexible system and away from static XML files.</p><h3>Kotlin code, but at what cost?</h3><p>However, the move from build time static XML files to generating your graph at runtime meant that the tools available to developers also changed significantly. The Navigation Component’s Safe Args Gradle Plugin, which generated type safe “Directions” classes you could use in your code to navigate between destinations, relied on reading the destinations and their arguments from those navigation XML files. That means without navigation XML files, there was no generated Safe Args code.</p><p>And while Navigation requires the correct types and arguments at runtime (telling you loudly (crashing) if you tried to pass a String to something expecting an Int or if you forgot a required argument), compile time safety was left as an exercise to the developer.</p><h3>Compile time type safety then</h3><p>Without the Safe Args Gradle plugin, what would you have left? The Kotlin DSL with Navigation Compose was based on the idea that each destination had a unique “route” — a RESTful path that uniquely identifies that destination.</p><p>For example, just like a website, you might have a &quot;home&quot; destination, a &quot;products&quot; destination, as well as pages that take arguments — the route for a particular product might be &quot;products/{productId}&quot; — it would include a placeholder for the unique ID of that product.</p><p>That meant:</p><ul><li>Keeping track of these string routes</li><li>Managing their arguments and their types</li><li>Worst of all, doing string interpolation</li></ul><p>Our <a href="https://developer.android.com/guide/navigation/design/type-safety">own documentation</a> and <a href="https://www.youtube.com/watch?v=goFpG25uoc8">video content</a> explored how to minimize this bookkeeping — isolating the strings alongside type safe extensions on top of our base Kotlin DSL. While this provided a strong barrier between the base Kotlin DSL and the API you expose across the rest of your code base and across different modules, it clearly wasn’t enough.</p><p>I’d like to personally thank the larger Android community for providing some fantastic solutions built on top of Navigation Compose designed to minimize or completely eliminate this manually written code including:</p><p><a href="https://medium.com/u/f6cdf228ca9a">Rafael Costa</a>’s <a href="https://composedestinations.rafaelcosta.xyz/">Compose Destinations</a> uses KSP to process annotations attached to composable functions to generate the entire navigation graph.</p><p>Kiwi.com’s <a href="https://github.com/kiwicom/navigation-compose-typed">navigation-compose-typed</a> uses Kotlin Serialization to generate routes directly from @Serializable objects or data classes.</p><h3>So many ways to generate code</h3><p>When looking at what ‘Safe Args’ would look like in our Kotlin DSL, we explored a number of approaches, essentially looking at many of the technologies available to us to generate type safe code.</p><p>One approach we explored was a transliteration of what we did with the Safe Args Gradle Plugin — rather than a Gradle plugin that would read the source of truth (your navigation XML file), we’d use your existing Kotlin code as the source of truth.</p><p>That meant if you wrote a piece of your Kotlin DSL that looked like:</p><pre>composable(<br>    route = &quot;profile/{userId}/{name}&quot;,<br>    arguments = listOf(<br>        navArgument(&quot;userId&quot;) {<br>            type = NavType.Int,<br>            nullable = false<br>        },<br>        navArgument(&quot;name&quot;) {<br>            type = NavType.String,<br>            nullable = true<br>        }<br>    )<br>) {</pre><p>We would generate the ProfileDestination and ProfileArgs classes you’d need to navigate to the &quot;profile&quot; destination and extract those arguments out. After looking into this….this was way easier said than done. Information like the string &quot;profile/{userId}/{name}&quot; was technically possible to extract, but only as a Kotlin Compiler Plugin. And even then, while we could find the route String passed to the Kotlin DSL, it was difficult to resolve the String if it was anything other than a constant String. Given that we have a number of Kotlin Compiler Plugin experts on our larger team who know exactly how much maintenance is involved in a compiler plugin (hi Compose folks!) and that we’re currently transitioning between the K1 and K2 compilers, we chose not to develop this solution any further.</p><p>So if the Kotlin DSL code wasn’t a viable source of truth, what could be a viable source of truth? And what tools were available to read that information? It turns out the other two big options (KSP and Kotlin Serialization) are also Kotlin Compiler Plugins. But importantly: they’re ones that ship alongside every version of Kotlin, which is critical for getting out of the way of developers eager to use new versions of Kotlin as they come out.</p><h3>Why Kotlin Serialization</h3><p>One of the guiding principles we’ve followed in developing the Navigation Component is in trying to minimize how ‘infectious’ Navigation code is: e.g., how easy is it to swap out our library for another (no judgment!). If you have Navigation code and classes spread throughout your entire code base in every file, you’re never going to get rid of it.</p><p>That’s why our <a href="https://developer.android.com/develop/ui/compose/navigation#testing">testing guide</a> specifically recommends avoiding having any references to your NavController in your screen level composable methods and specifically why there isn’t a NavController composition local: a button deep in your hierarchy, as convenient as it may be, should not be tied to your particular choice of navigation library.</p><p>So when looking for a ‘source of truth’ for how to define each destination in our graph, having each of those definitions totally independent of Navigation’s classes was exactly the type of approach we were looking for.</p><p>This meant that if you wanted to define a new destination in your navigation graph, you could write the simplest code possible:</p><pre>// Define a home destination that doesn&#39;t take any arguments<br>@Serializable<br>object Home<br><br>// Define a profile destination that takes an ID<br>@Serializable<br>data class Profile(val id: String)</pre><p>You’ll note that these purposefully don’t need to implement any Navigation provided interface or even be defined in a module that has a Navigation dependency. Yet, they are enough to encapsulate a meaningful name of the destination (I think you might get some looks if you named it object Object1) and any parameters that are core to the identity of that destination. That looks like it could be a viable source of truth.</p><p>With Kotlin Serialization as a viable source for compile time safety, we proceeded to take every API that took a String route and add Kotlin Serialization based overloads.</p><h3>Show me the code</h3><p>So having defined your Home and Profile Serializable classes, your graph now looks like:</p><pre>NavHost(navController, startDestination = Home) {<br>    composable&lt;Home&gt; {<br>        HomeScreen(onNavigateToProfile = { id -&gt;<br>            navController.navigate(Profile(id))<br>        })<br>     }<br>     composable&lt;Profile&gt; { backStackEntry -&gt;<br>         val profile: Profile = backStackEntry.toRoute()<br>         ProfileScreen(profile)<br>     }<br>}</pre><p>You should note one thing immediately: no strings! Specifically:</p><ul><li>No route string when defining a composable destination — specifying the type is enough to generate the route for you as well as the arguments (no more navArgument either)</li><li>No route string when navigating to a new destination. You pass NavController the Serializable object associated with the destination you want to navigate to.</li><li>No route string when defining the start destination of a navigation graph.</li></ul><p>For the Profile screen, we use the toRoute() extension method to recreate the Profile object from the NavBackStackEntry and its arguments. There’s a similar extension method on SavedStateHandle, making it just as easy to get the type safe arguments in your ViewModel as well without needing to reference specific argument keys.</p><p>This new approach applies to individual destinations, so you can incrementally migrate from your current approach to this new approach, one destination or one module at a time.</p><h3>Route vs Route Patterns</h3><p>One of my personal favorite features of this type safe approach is in making it very clear which APIs support a route pattern (e.g., &quot;profile/{id}&quot;) and which support a filled in route (e.g., &quot;profile/42&quot;). For instance, the popBackStack() API actually supports both, but that wasn’t clear when its parameter was just a String. With the type safe APIs, it is much clearer:</p><pre>// Pop up to the topmost instance of the Profile screen, inclusive<br>navController.popBackStack&lt;Profile&gt;(inclusive = true)<br><br>// Pop up to the exact instance of the Profile screen with ID 42<br>// also popping any other instances that are on top of it in the back stack<br>navController.popBackStack(Profile(42), inclusive = true)</pre><p>So when you see an API that takes a reified class, you’ll know that it denotes any destination of that type, irrespective of its arguments. While one that takes an actual instance of that class is used to find a specific destination with exactly those matching arguments.</p><p>APIs like getBackStackEntry() or even the startDestination of your graph are examples where technically they’ve supported both for some time and you might not have even known it!</p><h3>Custom types</h3><p>If you’re really doing something custom beyond the primitive types (or their Array and now List equivalents), complicated types like Parcelable types can even be used as fields on your Serializable classes by writing your own <a href="https://developer.android.com/guide/navigation/design/kotlin-dsl#custom-types">custom NavType</a> and passing it through when building your graph:</p><pre>// The Search screen requires more complicated parameters<br>@Parcelize<br>data class SearchParameters(<br>   val searchQuery: String,<br>   val filters: List&lt;String&gt;<br>)<br><br>@Serializable<br>data class Search(<br>   val parameters: SearchParameters<br>)<br><br>val SearchParametersType = object : NavType&lt;SearchParameters&gt;(<br>    isNullableAllowed = false<br>) {<br>    // See the custom NavType docs linked above for an<br>    //example of how to implement this<br>}<br><br>// Now use this in your destination<br>composable&lt;Search&gt;(<br> typeMap = mapOf(typeOf&lt;SearchParameters&gt;() to SearchParametersType)<br>) { backStackEntry -&gt;<br>    val searchParameters = backStackEntry.toRoute&lt;Search&gt;().parameters<br>}</pre><p>Note: this is <strong>supposed</strong> to be a speed bump: think long and hard whether an immutable, snapshot-in-time argument is really the source of truth for this data, or if this should really be an object you retrieve from a reactive source, such as a Flow exposed from a repository that would automatically refresh if your data changes.</p><h3>Go forth with safety</h3><p>The complete type safe API is available starting in <a href="https://developer.android.com/jetpack/androidx/releases/navigation#2.8.0-alpha08">Navigation 2.8.0-alpha08</a>. Besides support for all of the Kotlin DSL builders we support (including both Navigation Compose that we talked about here and Navigation with Fragments), it also includes other APIs you might find interesting like the navDeepLink API that takes a Serializable class and a prefix that allows you to easily connect external links to the same type safe APIs.</p><p>If you find any issues or have feature requests for APIs we missed, please <a href="https://issuetracker.google.com/issues/new?component=409828">file an issue</a> — while these APIs are still in alpha is the best time to request changes.</p><p>The code snippets in this blog have the following license:</p><pre>// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e081fb3cf2f8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/androiddevelopers/navigation-compose-meet-type-safety-e081fb3cf2f8">Navigation Compose meet Type Safety</a> was originally published in <a href="https://medium.com/androiddevelopers">Android Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Animations in Navigation Compose]]></title>
            <link>https://medium.com/androiddevelopers/animations-in-navigation-compose-36d48870776b?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/36d48870776b</guid>
            <category><![CDATA[navigation-component]]></category>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[jetpack-compose]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 04 Aug 2021 20:15:37 GMT</pubDate>
            <atom:updated>2022-08-18T17:43:52.510Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DlSgi-8GaQdn8E13nilcbw.png" /></figure><p>Jetpack Compose moves the bar on animations from ‘polish, if we have the time’ to ‘so easy there’s no reason to not do it’ and a big part of that are screen level transitions. That’s why <a href="https://developer.android.com/jetpack/compose/navigation">Navigation Compose</a> has been working towards a set of solutions that solve three specific cases:</p><ul><li>Using only the stable Animation APIs in Compose 1.0.0</li><li>Enabling support for Experimental Animation APIs present in Compose 1.0.0</li><li>Building towards the future Animation APIs (shared element transitions!!!) in Compose 1.1.0 and beyond</li></ul><p>Each of these requires a slightly different approach, which we’ll cover here.</p><h3>Compose 💚 Animations</h3><p>Jetpack Compose has come a long, long way since the first 0.1.0-dev01 release through to the new Compose 1.0.1 release. One of the areas that has been a huge improvement over the View world has been that of animations and transitions. In the quest for the perfect animation APIs, a lot of changes were made as Compose marched towards 1.0.0.</p><p>While a number of lower level Animation APIs like the incredibly powerful animateTo() and animate*AsState() are stable, foundational parts of Compose at this point, there’s a whole class of APIs on top of those building blocks marked with @ExperimentalAnimationApi.</p><h3>Experimental APIs and Semantic Versioning</h3><p>An Experimental API (any API using @RequiresOptIn API in Kotlin land) means that these APIs are subject to change at any point. This means that those APIs might be changed, improved, or replaced in any future release — maybe it is Compose 1.1.0-alpha04 or 1.2.0-alpha08. As such, any library that is built on those Experimental APIs would immediately crash and fail if you were to update the version of Compose you were using and not <em>also</em> update that library at the same time. (If you were along for the ride for early Compose releases, you know this pain.)</p><p>All AndroidX libraries, Navigation and Compose included, follow <a href="https://semver.org/">strict semantic versioning</a> as explained on the <a href="https://developer.android.com/jetpack/androidx/versions">AndroidX releases page</a>. This means that any API that isn’t experimental is set in stone once a library goes to its Release Candidate (RC) phase. It would take a major version bump (i.e., a ‘2.0’) to make breaking API changes to these stable APIs.</p><p>This is great when it comes to forward and backward compatibility — for instance, you can upgrade your Fragment version to try out a new alpha while keeping your other dependencies on their stable releases and everything just works.</p><p>However, it also means that experimental APIs, being APIs that can shift out from underneath you, are strictly forbidden across different artifact groups — again, upgrading your version of androidx.fragment shouldn’t break androidx.appcompat. This also applies to androidx.navigation and androidx.compose.animation.</p><h3>Getting Navigation 2.4 to stable</h3><p>Navigation 2.4 is a big release as both the first release of Navigation Compose as well as the first release with multiple back stack support for both Navigation Compose and Navigation with Fragments. This means we’re wrapping up the remaining related API requests in preparation for moving through beta, RC, and to stable.</p><p>For Navigation Compose, this means that we’re building on top of Compose 1.0.1 with the goal of being forward compatible for those of you who want to (or have already!) moved to start depending on Compose 1.1.0-alpha01 and beyond.</p><p>That forward compatibility requirement means that any code in Navigation Compose 2.4.0 can only rely on stable Compose Animation APIs. This is how we were able to add crossfade support in <a href="https://developer.android.com/jetpack/androidx/releases/navigation#2.4.0-alpha05">Navigation 2.4.0-alpha05</a> — in the world of Compose, jump cuts should be the first thing on your list to banish completely.</p><p>This limitation on only using stable Compose Animation APIs means that APIs such as <a href="https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#AnimatedContent(kotlin.Any,androidx.compose.ui.Modifier,kotlin.Function1,androidx.compose.ui.Alignment,kotlin.Function2)">AnimatedContent</a> aren’t something Navigation 2.4 can use directly to offer the kind of rich animation control you’d want directly as part of Navigation 2.4. However, the extensible nature of Navigation means that the underlying framework is already built and available.</p><h3>Introducing: Accompanist Navigation Animation!</h3><p>That underlying support for animating between destinations is why we’re able to release <a href="https://google.github.io/accompanist/navigation-animation/">Accompanist Navigation Animation</a>, built off of today’s release of <a href="https://developer.android.com/jetpack/androidx/releases/navigation#2.4.0-alpha06">Navigation 2.4.0-alpha06</a>. The Navigation Animation artifact provides its own set of animation enabled versions of the Navigation Compose APIs you’ve been using:</p><ul><li>Replace rememberNavController() with rememberAnimatedNavController()</li><li>Replace NavHost with AnimatedNavHost</li><li>Replace import androidx.navigation.compose.navigation with import com.google.accompanist.navigation.animation.navigation</li><li>Replace import androidx.navigation.compose.composable with import com.google.accompanist.navigation.animation.composable</li></ul><p>At first glance, the appearance of your app hasn’t changed: the default animations are still the same type of fadeIn and fadeOut that the crossfade found in Navigation 2.4 does for you. However, you’ll gain one crucial new feature: <strong>the ability to configure those animations and substitute in your own transitions between screens.</strong></p><p>This control comes in the form of four new parameters found on every composable destination:</p><ul><li>enterTransition: specifies the animation that runs when you navigate() to this destination.</li><li>exitTransition: specifies the animation that runs when you leave this destination by navigating to another destination.</li><li>popEnterTransition: specifies the animation that runs when this destination re-enters the screen after going through a popBackStack(). This defaults to the enterTransition.</li><li>popExitTransition: specifies the animation that runs when this destination leaves the screen after you pop it off the back stack. This defaults to the exitTransition.</li></ul><p>In each case, these parameters have the same format:</p><pre>enterTransition: (<br>  AnimatedContentScope&lt;NavBackStackEntry&gt;.() -&gt; EnterTransition?<br>)? = null,</pre><p>Each takes a lambda. That lambda uses the <a href="https://developer.android.com/reference/kotlin/androidx/compose/animation/AnimatedContentScope">AnimatedContentScope</a> to provide you with the NavBackStackEntry of where you are coming from (the initialState) and where you are going to (the targetState). For example, for the enterTransition, the entering destination is the targetState — the one you are applying the enterTransition to. The opposite applies to the exitTransition: the initialState screen is the one you are applying the exit transition to.</p><p>This allows you to write your destination such as:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c1e619159708636228e45674b5e182d8/href">https://medium.com/media/c1e619159708636228e45674b5e182d8/href</a></iframe><p>Or, control your animation based on where you’re coming from / going to:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9c85479e835ddb321beb66fb46388dd8/href">https://medium.com/media/9c85479e835ddb321beb66fb46388dd8/href</a></iframe><p>Here, the friends list screen controls its exit transition to the profile screen and the profile screen controls its enter transition from the friends list, allowing for a custom slide over animation between these two destinations. We also see the usage of null to mean “use the defaults”. Those defaults come from the parent navigation graph and then the parent’s parent’s navigation graph, all the way up the hierarchy to the root AnimatedNavHost. This means that setting default animations (say, the timing on crossfades) is possible just by changing the global enterTransition and exitTransition on your AnimatedNavHost.</p><p>If you instead want to change the default for only one subgraph (say, your login flow always uses a horizontal slide in animation), you can set that animation on the nested graph level as well:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/19e72dc4b126d620a349b3f8f0a89535/href">https://medium.com/media/19e72dc4b126d620a349b3f8f0a89535/href</a></iframe><p>Note how we use the <a href="https://developer.android.com/reference/kotlin/androidx/navigation/NavDestination#(androidx.navigation.NavDestination).hierarchy()">hierarchy extension method</a> to determine if the destination is actually part of the login graph — that way our transition <em>to</em> the login graph and <em>from</em> the login graph just use the default transition (or whatever transition you’ve set at the higher level).</p><p>Whenever you have a directional transition such as sliding in horizontally, this is where the difference between enterTransition and popEnterTransition becomes incredibly handy — you’ll be able to avoid cases where one screen is sliding to the right while the other slides to the left.</p><p>Accompanist serves as the booster rockets for Jetpack libraries and let us deliver experimental features <em>right now</em> as the work on Compose 1.1 progresses.</p><p>Add <a href="https://google.github.io/accompanist/navigation-animation/">Accompanist Navigation Animation</a> via:</p><pre>implementation<br>    &quot;com.google.accompanist:accompanist-navigation-animation:0.16.1&quot;</pre><h3>The future of Navigation Compose and Animations</h3><p>With Navigation 2.4 based on Compose 1.0.1 and Accompanist Navigation Animation stretching the limits of Compose 1.0 via experimental APIs, there’s something else on the horizon: Compose 1.1. Looking at the <a href="https://developer.android.com/jetpack/androidx/compose-roadmap">Compose Roadmap</a>, there’s one really important upcoming feature to get excited about:</p><blockquote>Support shared element transitions</blockquote><p>Our goal for Navigation 2.5 is to bring all the goodness of Compose 1.1 to Navigation Compose. That means as animation APIs lose their experimental status, we can fold them directly into Navigation Compose. It also means that we can build the API that we know will support shared element transitions as they become available.</p><p>It also means that Accompanist Navigation Animation should be considered as a temporary measure: once Navigation Compose itself offers the same level of animation APIs (tailored based on your feedback!), you’ll be able to depend on it directly and remove Accompanist Navigation Animation entirely.</p><h3>Go forth and animate</h3><p>Balancing stability and the forward and backward compatibility requirements we put on ourselves as a Jetpack library with the ability to ship features quickly means this isn’t as straightforward as we’d like. Accompanist has been a huge boon as Jetpack Compose gains momentum and accelerates beyond the need for those booster rockets. I’d like to thank <a href="https://medium.com/u/9303277cb6db">Chris Banes</a> and all the developers who put time into Accompanist, the entire team behind Compose, and all of you for helping shape the future of Android development.</p><p>PS: if you’re looking for even more Navigation+Accompanist goodies, check out the also brand new <a href="https://google.github.io/accompanist/navigation-material/">Accompanist Navigation Material</a>!</p><p><a href="https://jossiwolf.medium.com/introducing-navigation-material-%EF%B8%8F-a19ed5cc33fd">Introducing Navigation-Material 🧭🎨️</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=36d48870776b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/androiddevelopers/animations-in-navigation-compose-36d48870776b">Animations in Navigation Compose</a> was originally published in <a href="https://medium.com/androiddevelopers">Android Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multiple back stacks]]></title>
            <link>https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/b714d974f134</guid>
            <category><![CDATA[android-development]]></category>
            <category><![CDATA[jetpack]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Mon, 07 Jun 2021 16:10:03 GMT</pubDate>
            <atom:updated>2021-06-07T17:03:47.658Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5-lbc-YBJlZnxVFPvNMPAQ.png" /></figure><h4>A deep dive into what actually went into this feature</h4><p>If a ‘back stack’ is a set of screens that you can navigate back through via the system back button, ‘multiple back stacks’ is just a bunch of those, right? Well, that’s exactly what we’ve done with the multiple back stack support added in <a href="https://developer.android.com/jetpack/androidx/releases/navigation#2.4.0-alpha01">Navigation 2.4.0-alpha01</a> and <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.4.0-alpha01">Fragment 1.4.0-alpha01</a>!</p><h3>The joys of the system back button</h3><p>Whether you’re using Android’s new gesture navigation system or the traditional navigation bar, the ability for users to go ‘back’ is a key part to the user experience on Android and doing that right is an important part to making your app feel like a natural part of the ecosystem.</p><p>In the simplest cases, the system back button just finishes your activity. While in the past you might have been tempted to override the onBackPressed() method of your activity to customize this behavior, it is 2021 and that is totally unnecessary. Instead, there are <a href="https://developer.android.com/guide/navigation/navigation-custom-back">APIs for custom back navigation</a> in the <a href="https://developer.android.com/reference/androidx/activity/OnBackPressedDispatcher">OnBackPressedDispatcher</a>. This is actually the same API that <a href="https://developer.android.com/reference/androidx/fragment/app/FragmentManager">FragmentManager</a> and <a href="https://developer.android.com/reference/kotlin/androidx/navigation/NavController">NavController</a> <strong>already</strong> plug into.</p><p>That means when you use either Fragments or Navigation, they use the OnBackPressedDispatcher to ensure that if you’re using their back stack APIs, the system back button works to reverse each of the screens that you’ve pushed onto the back stack.</p><p>Multiple back stacks doesn’t change these fundamentals. The system back button is still a one directional command — ‘go back’. This has a profound effect on how the multiple back stack APIs work.</p><h3>Multiple back stacks in Fragments</h3><p>At the surface level, the <a href="https://developer.android.com/guide/fragments/fragmentmanager#multiple-back-stacks">support for multiple back stacks</a> is deceptively straightforward, but requires a bit of an explanation of what actually is the ‘fragment back stack’. The FragmentManager’s back stack isn’t made up of fragments, but instead is made up of fragment transactions. Specifically, the ones that have used the <a href="https://developer.android.com/reference/androidx/fragment/app/FragmentTransaction#addToBackStack(java.lang.String)">addToBackStack(String name)</a> API.</p><p>This means when you commit() a fragment transaction with addToBackStack(), the FragmentManager is going to execute the transaction by going through and executing each of the operations (the replace, etc.) that you specified on the transaction, thus moving each fragment through to its expected state. FragmentManager then holds onto that transaction as part of its back stack.</p><p>When you call popBackStack() (either directly or via FragmentManager’s integration with the system back button), the topmost transaction on the fragment back stack is reversed — an added fragment is removed, a hidden fragment is shown, etc. This puts the FragmentManager back into the same state that it was before the fragment transaction was initially committed.</p><blockquote><strong>Note</strong>: I cannot stress this enough, but you absolutely should never interleave transactions with addToBackStack() and transactions without in the same FragmentManager: transactions on your back stack are blissfully unaware of non-back stack changing fragment transactions — swapping things out from underneath those transactions makes that reversal when you pop a much more dicey proposition.</blockquote><p>This means that popBackStack() is a destructive operation: any added fragment will have its state destroyed when that transaction is popped. This means you lose your view state, any saved instance state, and any ViewModel instances you’ve attached to that fragment are cleared. This is the main difference between that API and the new saveBackStack(). saveBackStack() does the same reversal that popping the transaction does, but it ensures that the view state, saved instance state, and ViewModel instances are all saved from destruction. This is how the restoreBackStack() API can later recreate those transactions and their fragments from the saved state and effectively ‘redo’ everything that was saved. Magic!</p><p>This didn’t come without paying down a lot of technical debt though.</p><h3>Paying down our technical debts in Fragments</h3><p>While fragments have always saved the <a href="https://developer.android.com/guide/fragments/saving-state#view">Fragment’s view state</a>, the only time that a fragment’s onSaveInstanceState() would be called would be when the Activity’s onSaveInstanceState() was called. To ensure that the saved instance state is saved when calling saveBackStack(), we need to <strong>also</strong> inject a call to onSaveInstanceState() at the right point in the <a href="https://developer.android.com/guide/fragments/lifecycle#states">fragment lifecycle transitions</a>. We can’t call it too soon (your fragment should never have its state saved while it is still STARTED), but not too late (you want to save the state before the fragment is destroyed).</p><p>This requirement kicked off a process to <a href="https://issuetracker.google.com/139536619">fix how </a><a href="https://issuetracker.google.com/139536619">FragmentManager moves to state</a> to make sure there’s one place that manages moving a fragment to its expected state and handles re-entrant behavior and all the state transitions that go into fragments.</p><p>35 changes and 6 months into that restructuring of fragments, it turned out that <a href="https://issuetracker.google.com/147749580">postponed fragments were seriously broken</a>, leading to a world where postponed transactions were left floating in limbo — not actually committed and not actually not committed. Over 65 changes and another 5 months later, and we had completely rewritten most of the internals of how FragmentManager manages state, postponed transitions, and animations. That effort is covered in more detail in my previous blog post:</p><p><a href="https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e">Fragments: Rebuilding the Internals</a></p><h3>What to expect in Fragments</h3><p>With the technical debt paid down (and a much more reliable and understandable FragmentManager), the tip of the iceberg APIs of saveBackStack() and restoreBackStack() were added.</p><p>If you don’t use these new APIs, nothing changes: the single FragmentManager back stack works as before. The existing addToBackStack() API remains unchanged — you can use a null name or any name you want. However, that name takes on a new importance when you start looking at multiple back stacks: it is that name that is the unique key for that fragment transaction that you’d use with saveBackStack() and later with restoreBackStack().</p><p>This might be easier to see in an example. Let’s say that you have added an initial fragment to your activity, then done two transactions, each with a single replace operation:</p><pre>// This is the initial fragment the user sees<br>fragmentManager.commit {<br>  setReorderingAllowed(true)<br>  replace&lt;HomeFragment&gt;(R.id.fragment_container)<br>}</pre><pre>// Later, in response to user actions, we’ve added two more<br>// transactions to the back stack<br>fragmentManager.commit {<br>  setReorderingAllowed(true)<br>  replace&lt;ProfileFragment&gt;(R.id.fragment_container)<br>  addToBackStack(“profile”)<br>}</pre><pre>fragmentManager.commit {<br>  setReorderingAllowed(true)<br>  replace&lt;EditProfileFragment&gt;(R.id.fragment_container)<br>  addToBackStack(“edit_profile”)<br>}</pre><p>This means that our FragmentManager looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/805/1*pYX8n8VUsWPKxzRQTGx0zg.png" /><figcaption>FragmentManager state after three commits</figcaption></figure><p>Let’s say that we want to swap out our profile back stack and swap to the notifications fragment. We’d call saveBackStack() followed by a new transaction:</p><pre>fragmentManager.saveBackStack(&quot;profile&quot;)</pre><pre>fragmentManager.commit {<br>  setReorderingAllowed(true)<br>  replace&lt;NotificationsFragment&gt;(R.id.fragment_container)<br>  addToBackStack(&quot;notifications&quot;)<br>}</pre><p>Now our transaction that added the ProfileFragment and the transaction that added the EditProfileFragment has been saved under the &quot;profile&quot;<br> key. Those fragments have had their state saved completely and FragmentManager is holding onto their state alongside the transaction state. Importantly: those fragment instances no longer exist in memory or in the FragmentManager — it is just the state (and any non config state in the form of ViewModel instances):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/805/1*cG_ot3MkNMb--5BVvrIoCQ.png" /><figcaption>FragmentManager state after we’ve saved the profile back stack and added one more commit</figcaption></figure><p>Swapping back is simple enough: we can do the same saveBackStack() operation on our &quot;notifications&quot; transaction and then restoreBackStack():</p><pre>fragmentManager.saveBackStack(“notifications”)</pre><pre>fragmentManager.restoreBackStack(“profile”)</pre><p>The two stacks have effectively swapped positions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/805/1*xr1O7MJNWzWPO9uK88IdRA.png" /><figcaption>FragmentManager state after swapping the two stacks</figcaption></figure><p>This style of maintaining a single active back stack and swapping transactions onto it ensures that the FragmentManager and the rest of the system always has a consistent view of what actually is supposed to happen when the system back button is tapped. In fact, that logic remained entirely unchanged: it still just pops the last transaction off of the fragment back stack like before.</p><p>These APIs are purposefully minimal, despite their underlying effects. This makes it possible to build your own structure on top of these building blocks while avoiding any hacks to save Fragment view state, saved instance state, and non config state.</p><p>Of course, if you don’t want to build your own structure on top of these APIs, you can also use the one we provide.</p><h3>Bringing multiple back stacks to any screen type with Navigation</h3><p>The <a href="https://developer.android.com/guide/navigation/">Navigation Component</a> was built <strong>from the beginning</strong> as a generic runtime that knows nothing about Views, Fragments, Composables, or any other type of screen or ‘destination’ you might implement within your activity. Instead, it is the responsibility of an implementation of the <a href="https://developer.android.com/reference/kotlin/androidx/navigation/NavHost">NavHost interface</a> to add one or more <a href="https://developer.android.com/reference/kotlin/androidx/navigation/Navigator">Navigator</a> instances that <strong>do</strong> know how to interact with a particular type of destination.</p><p>This meant that the logic for interacting with fragments was entirely encapsulated in the navigation-fragment artifact and its FragmentNavigator and DialogFragmentNavigator. Similarly the logic for interacting with Composables is in the completely independent navigation-compose artifact and its ComposeNavigator. That abstraction means that if you want to build your app solely with Composables, you are not forced to pull in any dependency on fragments when you use Navigation Compose.</p><p>This level of separation means that there are really two layers to multiple back stacks in Navigation:</p><ul><li>Saving the state of the individual <a href="https://developer.android.com/reference/kotlin/androidx/navigation/NavBackStackEntry">NavBackStackEntry</a> instances that make up the NavController back stack. This is the responsibility of the NavController.</li><li>Saving any Navigator specific state associated with each NavBackStackEntry (e.g., the fragment associated with a FragmentNavigator destination). This is the responsibility of the Navigator.</li></ul><p>Special attention was given to the cases where the Navigator has <strong>not</strong> been updated to support saving its state. While the underlying Navigator API was entirely rewritten to support saving state (with new overloads of its navigate() and popBackStack() APIs that you should override instead of the previous versions), NavController will save the NavBackStackEntry state even if the Navigator has not been updated (backward compatibility is a big deal in the Jetpack world!).</p><blockquote>PS: this new Navigator API also makes it way easier to test your own custom Navigator in isolation by attaching a <a href="https://developer.android.com/reference/kotlin/androidx/navigation/testing/TestNavigatorState">TestNavigatorState</a> that acts as a mini-NavController.</blockquote><p>If you’re just using Navigation in your app, the Navigator level is more of an implementation detail than something you’ll ever need to interact with directly. Suffice it to say, we’ve already done the work required to get the FragmentNavigator and the ComposeNavigator over to the new Navigator APIs so that they correctly save and restore their state; there’s no work you need to do at that level.</p><h3>Enabling multiple back stacks in Navigation</h3><p>If you’re using <a href="https://developer.android.com/guide/navigation/navigation-ui">NavigationUI</a>, our set of opinionated helpers for connecting your NavController to Material view components, you’ll find that multiple back stacks is <strong>enabled by default</strong> for menu items, BottomNavigationView (and now NavigationRailView!), and NavigationView. This means that the common combination of using navigation-fragment and navigation-ui will <em>just work</em>.</p><p>The NavigationUI APIs are purposefully built on top of the other public APIs available in Navigation, ensuring that you can build your own versions for precisely your set of custom components you want. The APIs to enable saving and restoring a back stack are no exception to this, with new APIs on NavOptions, the navOptions Kotlin DSL, in the Navigation XML, and in an overload for popBackStack() that let you specify that you want a pop operation to save state or you want a navigate operation to restore some previously saved state.</p><p>For example, in Compose, any global navigation pattern (whether it is a bottom navigation bar, navigation rail, drawer, or anything you can dream up) can all use the same technique as we show for <a href="https://developer.android.com/jetpack/compose/navigation#bottom-nav">integrating with BottomNavigation</a> and call navigate() with the saveState and restoreState attributes:</p><pre>onClick = {<br>  navController.navigate(screen.route) {<br>    // Pop up to the start destination of the graph to<br>    // avoid building up a large stack of destinations<br>    // on the back stack as users select items<br>    popUpTo(navController.graph.findStartDestination().id) {<br>      saveState = true<br>    }<br><br>    // Avoid multiple copies of the same destination when<br>    // reselecting the same item<br>    launchSingleTop = true</pre><pre>    // Restore state when reselecting a previously selected item<br>    restoreState = true<br>  }<br>}</pre><h3>Save your state, save your users</h3><p>One of the most frustrating things for a user is losing their state. That’s one of the reasons why fragments have a whole page on <a href="https://developer.android.com/guide/fragments/saving-state">saving state</a> and one of the many reasons why I am so glad to get each layer updated to support multiple back stacks:</p><ul><li>Fragments (i.e., without using the Navigation Component at all): this is an opt-in change by using the new FragmentManager APIs of saveBackStack and restoreBackStack.</li><li>The core Navigation Runtime: adds opt-in new NavOptions methods for restoreState and saveState and a new overload of popBackStack() that also accepts a saveState boolean (defaults to false).</li><li>Navigation with Fragments: the FragmentNavigator now utilizes the new Navigator APIs to properly translate the Navigation Runtime APIs into the Fragment APIs by using the Navigation Runtime APIs.</li><li>NavigationUI: The onNavDestinationSelected(), NavigationBarView.setupWithNavController(), and NavigationView.setupWithNavController() now use the new restoreState and saveState NavOptions <strong>by default</strong> whenever they would pop the back stack. This means that <strong>every app using those </strong><strong>NavigationUI APIs will get multiple back stacks without any code changes on their part after upgrading the Navigation 2.4.0-alpha01 or higher.</strong></li></ul><p>If you’d like to look at some more examples that use this API, take a look at the NavigationAdvancedSample (newly updated without any of the NavigationExtensions code it used to require to support multiple back stacks):</p><p><a href="https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample">android/architecture-components-samples</a></p><p>And for Navigation Compose, consider looking at Tivi:</p><p><a href="https://github.com/chrisbanes/tivi">GitHub - chrisbanes/tivi: Tivi is a TV show tracking Android app, which connects to trakt.tv</a></p><p>If you do run into any issues, please make sure to use the official issue tracker to file bugs against <a href="https://issuetracker.google.com/issues/new?component=460964">Fragments</a> or <a href="https://issuetracker.google.com/issues/new?component=409828">Navigation</a> and we’ll be sure to take a look at them!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b714d974f134" width="1" height="1" alt=""><hr><p><a href="https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134">Multiple back stacks</a> was originally published in <a href="https://medium.com/androiddevelopers">Android Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fragments: Rebuilding the Internals]]></title>
            <link>https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/61913f8bf48e</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[fragments]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 19 Aug 2020 16:51:09 GMT</pubDate>
            <atom:updated>2020-10-15T17:34:07.887Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ir8CdY5D5Do5R_22Vo3uew.png" /></figure><h3>Fragments: rebuilding the internals</h3><h4>Introducing: the new state manager</h4><p>Fragments, more than most Android APIs, have evolved very organically over the years. They started as part of the Android platform itself, became a mirrored existence in the Android platform and as part of the Android Support Library, and now exist solely as part of Jetpack as <a href="https://developer.android.com/jetpack/androidx/releases/fragment">AndroidX Fragments</a>.</p><blockquote><strong>Note</strong>: you should under no circumstances use the Android framework version of Fragments. Besides being fully deprecated in Android 10, they weren’t receiving fixes for a considerable amount of time before that and, being baked into the framework, no backporting of fixes or consistency across devices and API levels can be expected.</blockquote><p>While <a href="https://developer.android.com/topic/libraries/architecture">Architecture Components</a> have taken over many of the roles that traditionally needed a Fragment (such as using a LifecycleObserver for Lifecycle callbacks or a ViewModel for retained state), if you’re using Fragments, you’re adding, removing, and interacting with them through a FragmentManager.</p><p>With <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08">Fragment </a><a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08">1.3.0-alpha08</a>, some of the most significant restructuring of the internals of FragmentManager have been completed. This release swaps out much of the logic that used to live directly in FragmentManager with smaller, testable, and maintainable (internal) classes, the core of which is <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java?ss=androidx">FragmentStateManager</a>.</p><blockquote><strong>Note: </strong>I’m going to be talking a lot about the internals of FragmentManager in this post. TL/DR: please pay extra attention to regression testing with Fragment 1.3.0-alpha08 and <a href="https://issuetracker.google.com/issues/new?component=460964">file issues</a> as soon as possible if you discover any regressions.</blockquote><p>This new state manager is responsible for some pretty key parts of Fragments:</p><ul><li>Moving Fragments through their lifecycle methods</li><li>Running animations and transitions</li><li>Handling postponed transactions</li></ul><p>We’ve taken a ground up look at how those systems previously work, found <a href="https://issuetracker.google.com/issues/147749580">them wanting</a>, and rewrote them from scratch. They’re now better than ever, we were able to close out 10+ long standing related issues, and the internal restructuring has cleared the way to <a href="https://issuetracker.google.com/issues/80029773">build support for multiple back stacks in a single </a><a href="https://issuetracker.google.com/issues/80029773">FragmentManager</a> and <a href="https://youtu.be/RS1IACnZLy4?t=956">simplify the Fragment lifecycle</a>.</p><h3>FragmentManager’s moveToState()</h3><p>Each FragmentManager is associated with a host. In the vast majority of cases for fragments, this is a FragmentActivity (there is an entire layer of FragmentController and FragmentHostCallback for building your own custom host, but let’s avoid that discussion here). As the activity moves to CREATED, STARTED, and RESUMED, FragmentManager dispatches those changes down to its fragments. This is the role of moveToState().</p><p>Of course, it isn’t quite that straightforward. There is a lot of conditional logic to determine exactly what state the fragment should be in — the activity lifecycle state (or the parent fragment’s state for nested fragments) is only the first part and serves as the maximum state the fragment can be in. This maximum is there to ensure that the lifecycle of the activity, fragments, and their child fragments are all properly nested.</p><p>So our first order of business in <a href="https://issuetracker.google.com/139536619">simplifying </a><a href="https://issuetracker.google.com/139536619">moveToState()</a> was to collapse all of that logic into one place. Thus was born <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java?ss=androidx">FragmentStateManager</a>. Each fragment instance is tied to a FragmentStateManager under the hood. By introducing this class internally, we were able to take much of code that interacts with the fragment (such as calling the fragment’s onCreateView and other lifecycle methods) out of FragmentManager itself.</p><p>That split also allowed us to write a single method that would take all of the backward compatible required logic for what state the fragment should actually be in and centralize it in one place: <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/FragmentStateManager.java;l=165?ss=androidx">computeExpectedState()</a>. This one method keeps track of all of the current state and determines what state the fragment should be in. 98% of the time, it is the same state as the host / parent fragment, but that 2% makes a big difference to those apps built on fragments.</p><p>However, we ran into one case where we didn’t have a way to determine the right state: postponed fragments.</p><h3>Postponed fragments</h3><p>Fragments, for better or worse, inherited a lot of the same nomenclature and API surface as activities. Part of this inheritance was around transitions and the ability to postpone your enter transition until you’re ready. This is critical to shared element transitions (where you really want to have an image loaded to know its dimensions and position on the screen before starting the transition over to that location), but also allows you to ensure that more intensive loading calls don’t happen at the same time as your transition, avoiding jank.</p><p>A postponed fragment has two important qualities:</p><ol><li>Its view was created, but is not visible</li><li>Its lifecycle is capped at STARTED</li></ol><p>As soon as you call startPostponedEnterTransition(), the fragment’s transition would run, the view would become visible, and the fragment would be able to move to RESUMED. This is, in fact, exactly what the new state manager does, but it was not how Fragments worked before. To quote the <a href="https://issuetracker.google.com/147749580">Postponed Fragments leave the Fragments and FragmentManager in an inconsistent state bug</a>:</p><blockquote>When a Fragment is postponed using postponeEnterTransition(), the expected behavior is that the container the Fragment is added to does not run any enter animations or previously queued up exit animations (i.e., for a replace() operation) until the Fragment calls startPostponedEnterTransition(). It is also expected that the Fragment does not reach the RESUMED state while its container is postponed.</blockquote><blockquote>However, it seems like FragmentManager isn’t <em>just</em> doing that, but instead is moving the Fragment and the whole FragmentManager into a weird, inconsistent state.</blockquote><blockquote>Namely, any FragmentTransaction that touches the container of the postponed Fragment is ‘rolled back’ (i.e., done in reverse), but those Fragments aren’t actually moved to their proper state.</blockquote><p>This led to a litany of issues:</p><ul><li>The Fragment’s view is created, but the fragment isn’t added (isAdded() returns false)</li><li>findFragmentById() doesn&#39;t return the newly added Fragment over the one it replaces, even when using commitNow()</li><li>Fragments stuck in this limbo state don’t get started when the FragmentManager is started (<a href="https://issuetracker.google.com/issues/129035555">https://issuetracker.google.com/issues/129035555</a>)</li><li>FragmentTransactions can be executed out of order (<a href="https://issuetracker.google.com/issues/147297731">https://issuetracker.google.com/issues/147297731</a>)</li><li>Other animations on the container (such as previously started pop animations) still run (<a href="https://issuetracker.google.com/issues/37140383">https://issuetracker.google.com/issues/37140383</a>)</li><li>onCreateView() can be called a second time (<a href="https://issuetracker.google.com/issues/143915710">https://issuetracker.google.com/issues/143915710</a>)</li></ul><p>Actually fixing any of these issues meant replacing the entire roll back process used by postponed fragments with a system that keeps the FragmentManager in a consistent, up to date state, while still maintaining the important qualities of postponed fragments.</p><h3>Working at the container level</h3><p>FragmentManager has this nice (read: handy, but not fun as the maintainer) property where it lets you pass in any container ID for where you want to place a Fragment. Even for a single FragmentTransaction, you can add a fragment to one container, remove another from a different container, replace a third container’s topmost fragment, etc. The rub comes in when it comes to animating in/out the fragments — something that happens solely at the container level.</p><p>Fragments support a number of animating systems:</p><ul><li>The old and <a href="https://issuetracker.google.com/163084315#comment4">busted</a> framework Animation API</li><li>The framework Animator API</li><li>The framework Transition API (only API 21+, also pretty busted)</li><li>The <a href="https://developer.android.com/jetpack/androidx/releases/transition">AndroidX </a><a href="https://developer.android.com/jetpack/androidx/releases/transition">Transition</a> API</li></ul><p>As you might know, naming is one of the hardest problems in Computer Science, so when we went to build a class that could control all of these APIs, it took a while to settle on <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.java">SpecialEffectsControlle</a>r (this class isn’t part of the public API, so names are still subject to change, thankfully). This class exists on the container level and coordinates all of the “special effects” associated with entering and exiting fragments.</p><p>The SpecialEffectsController is the single source of truth on what should be happening to that container. This means that if the topmost added fragment is postponed, the entire container is postponed. There’s no more logic needed at the FragmentManager layer, nor any rollback of transactions (which, as we mentioned, can affect multiple containers). Thus, the FragmentManager is in the correct state and we still get all of the special properties of postponed fragments.</p><p>This base API then allowed us to centralize all of the crazy special effects APIs that fragment has into a single <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java">DefaultSpecialEffectsController</a> that is responsible for running transitions and animations and animators. Again, moving logic that used to be scattered across FragmentManager into a single place.</p><h3>So what does a ‘new state manager’ mean</h3><p>Well, it means that instead of this architecture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*RRKkZEAluuo4L3gewjvZwA.png" /><figcaption>The old state manager: everything is in FragmentManager</figcaption></figure><p>It looks more like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*xxLsF2sNZNH0dqnqdfT_dQ.png" /><figcaption>The new state manager: FragmentManager talks to individual FragmentStateManager instances, which coordinate with other fragments in their container via SpecialEffectsController</figcaption></figure><p>By splitting up the internals of FragmentManager, the logic has been greatly simplified at each layer:</p><ul><li>The FragmentManager only has state that applies to all fragments</li><li>The FragmentStateManager manages the state at the fragment level</li><li>The SpecialEffectsController manages the state at the container level</li></ul><p>This separation of responsibilities has let us expand our test suite by almost 30%, covering many more scenarios that were near impossible to test in isolation.</p><h3>Should I expect behavior changes?</h3><p><strong>No</strong>. In fact, we run a significant portion of the internal fragment tests against both the old and new state managers specifically to ensure we have a strong set of regression tests in place.</p><p>However, if you were relying on the inconsistent state a postponed fragment put the FragmentManager into, then yeah, you’ll find that you now actually get the correct state. You’ll find the list of bug fixes associated with the new state manager as part of the <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08">release notes</a>, so take a look through that to make sure that your issues aren’t caused by your own workarounds for the old broken behavior that you can now just remove.</p><p>Similar to the changes to onDestroyView timing in <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.2.0">Fragment </a><a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.2.0">1.2.0</a>, the new state manager will keep your fragment in the STARTED state until its transitions/animations/animators/special effects all finish, thus bringing consistency across all fragments, whether they are postponed directly or postponed because of other fragments in that same container.</p><h3>What if I *do* see behavior changes?</h3><p>After you upgrade to <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08">Fragment </a><a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha08">1.3.0-alpha08</a>, the new state manager is enabled by default. If you see differences in your app, the first step is to see if it is related to the new state manager by using the new experimental API:</p><pre>FragmentManager.enableNewStateManager(false)</pre><p>This API is an escape hatch back into the old world and lets you verify that any changes you’re seeing are tied to the new state manager. This unblocks you from upgrading the Fragment 1.3.0-alpha08 and lets you build a sample project that reproduces the issue that you can attach when you <a href="https://issuetracker.google.com/issues/new?component=460964">file an issue against Fragments</a>.</p><blockquote>Note: the FragmentManager.enableNewStateManager() API is <strong>experimental</strong>. That means that it is not considered part of the stable API surface of Fragments and can be removed at <strong>any</strong> point. Removing all of the old code is a significant code reduction, but given the importance of getting this right, we likely won’t remove the API until after the stable release of Fragment 1.3.0 — i.e., consider it on notice for removal in a Fragment 1.3.1 release.</blockquote><p>With over 100 individual changes over an 11 month period, this is absolutely the largest internal change to Fragments in a while and sets us up for a much more maintainable, sustainable, and understandable code base. That means more consistent behavior across Fragments and a firm base you can rely on when building your app. We’d appreciate any help you can offer in making sure this new state manager is the best it can be by continuing to <a href="https://issuetracker.google.com/issues/new?component=460964">file issues</a> and offering feedback.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=61913f8bf48e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e">Fragments: Rebuilding the Internals</a> was originally published in <a href="https://medium.com/androiddevelopers">Android Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Muzei 3.4]]></title>
            <link>https://medium.com/muzei/muzei-3-4-33fb5c2b6b88?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/33fb5c2b6b88</guid>
            <category><![CDATA[muzei]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[android-app-development]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 29 Jul 2020 16:29:47 GMT</pubDate>
            <atom:updated>2020-08-15T22:13:32.937Z</atom:updated>
            <content:encoded><![CDATA[<p>Muzei 3.4 is <a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">now available on Google Play</a> and with it comes new functionality and <a href="https://github.com/romannurik/muzei/releases/tag/api3.4.0">Muzei API 3.4</a>, which enables your Source to take advantage of many of these features.</p><h3>Commands get an upgrade</h3><p>Since the first release of Muzei, sources have been able to provide custom commands. Each command would appear in the overflow menu. When the user tapped the command, it would trigger the onCommand() method of your MuzeiArtProvider.</p><p>This approach had two big issues:</p><ol><li>It was hard for users to find a command behind the overflow menu.</li><li>Due to <a href="https://developer.android.com/guide/components/activities/background-starts">restrictions on starting activities from the background</a>, any attempts to start an activity from onCommand() would fail on Android 10 and higher devices.</li></ol><p>Muzei 3.4 address both of these concerns via a new API based on <a href="https://developer.android.com/reference/androidx/core/app/RemoteActionCompat">RemoteActionCompa</a>t which you can see as soon as you load up Muzei 3.4.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DgYDxCyo-HD1j2KmJurYPQ.png" /><figcaption>Sources can now display their commands as icons</figcaption></figure><p>Yep, the new <a href="http://api.muzei.co/reference/com.google.android.apps.muzei.api.provider/-muzei-art-provider/get-command-actions.html">getCommandActions()</a> API lets you associate an icon with your command and respects the use of <a href="https://developer.android.com/reference/androidx/core/app/RemoteActionCompat#setShouldShowIcon(boolean)">setShowAsIcon(true)</a> to show your command as an icon right next to the Next Artwork command (there is a limit to how many icons can be displayed, so your action may still be displayed in the overflow menu if you overuse this functionality).</p><p>The new API also uses a PendingIntent to trigger your command. This means that launching an activity from a command just means you use PendingIntent.getActivity and that works exactly the same across all API levels (even on the latest versions of Android) without the need to use Intent.FLAG_NEW_TASK either. Similarly, if you have background work, you’d use PendingIntent.getBroadcast or PendingIntent.getService to do your work.</p><h4>Examples of custom commands</h4><p>The <a href="https://github.com/romannurik/muzei/blob/api3.4.0/example-unsplash/src/main/java/com/example/muzei/unsplash/UnsplashExampleArtProvider.kt">UnsplashExampleArtProvider</a> has two commands — one that shows as an icon to go to the author’s Unsplash profile and a second that lets you visit Unsplash to explore more artwork.</p><p>Creating the profile command is based on the Artwork passed to getCommandActions() (remember, each Artwork can have separate commands):</p><pre>private fun createViewProfileAction(<br>    context: Context,<br>    artwork: Artwork<br>): RemoteActionCompat {<br>    val profileUri = artwork.metadata?.toUri() ?: return null<br>    val title = context.getString(R.string.action_view_profile,<br>        artwork.byline)<br>    val intent = Intent(Intent.ACTION_VIEW, profileUri)<br>    return RemoteActionCompat(<br>            IconCompat.createWithResource(context,<br>                R.drawable.source_ic_profile),<br>            title,<br>            title, // content description for the tooltip<br>            PendingIntent.getActivity(context, 0, intent,<br>                PendingIntent.FLAG_UPDATE_CURRENT))<br>}</pre><p>Here, the Unsplash Example source has used the <a href="http://api.muzei.co/reference/com.google.android.apps.muzei.api.provider/-artwork/metadata.html">metadata</a> field to store the profile URI for the user. For the icon, RemoteActionCompat relies on IconCompat, which lets you build an icon from a bitmap, content:// URI, a byte array, or the most common case: a resource ID. Each icon should be 24x24dp. You’ll provide a title (which will appear if the action is in the overflow menu) and a content description (which will be used as the tooltip for the icon) and the PendingIntent that should be triggered when the command is tapped.</p><p>Creating the visit Unsplash action for the overflow menu is very similar with constructing the RemoteActionCompat:</p><pre>private fun createVisitUnsplashAction(<br>    context: Context<br>): RemoteActionCompat {<br>    val title = context.getString(R.string.action_visit_unsplash)<br>    val unsplashUri = context.getString(R.string.unsplash_link)<br>    val intent = Intent(Intent.ACTION_VIEW, unsplashUri.toUri())<br>    return RemoteActionCompat(<br>            IconCompat.createWithResource(context,<br>                R.drawable.muzei_launch_command),<br>            title,<br>            title,<br>            PendingIntent.getActivity(context, 0, intent,<br>                PendingIntent.FLAG_UPDATE_CURRENT)).apply {<br>        setShouldShowIcon(false)<br>    }<br>}</pre><p>Muzei API 3.4 ships with an R.drawable.muzei_launch_command icon which can be used as a default icon for your command. You’ll note that we need to explicitly call setShowShowIcon(false) — the default for a RemoteActionCompat is true, so you’ll need to set it to false if you always want your command to only appear in the overflow menu.</p><p>By extracting these out into their own methods, getCommandActions() can simply return a listOf() including the two RemoteActionCompat instances returned by the methods.</p><h3>Integration with the File Picker</h3><p>Android has provided a mechanism for integrating your own app into the default file picker and Files app since API 19 in the form of <a href="https://developer.android.com/guide/topics/providers/create-document-provider">a custom </a><a href="https://developer.android.com/guide/topics/providers/create-document-provider">DocumentsProvider</a>. But actually implementing one is easier said than done.</p><p>To make it easy to expose artwork from your MuzeiArtProvider to the default file picker, Muzei API 3.4 now allows you to add a prebuilt <a href="http://api.muzei.co/reference/com.google.android.apps.muzei.api.provider/-muzei-art-documents-provider/index.html">MuzeiArtDocumentsProvider</a> to your manifest:</p><pre>&lt;provider android:name=&quot;com.google.android.apps.muzei.api.provider.MuzeiArtDocumentsProvider&quot;<br>  android:authorities=&quot;${yourAuthority}.documents&quot;<br>  android:exported=&quot;true&quot;<br>  android:grantUriPermissions=&quot;true&quot;<br>  android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;&gt;<br>  &lt;intent-filter&gt;<br>      &lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;<br>  &lt;/intent-filter&gt;<br>&lt;/provider&gt;</pre><p>You’ll note the android:authorities — this needs to match the android:authorities on your MuzeiArtProvider entry in your manifest plus the .documents suffix. With just that, the two providers will automatically be kept in sync. There’s no extra work you need to do besides opting in to this new API.</p><blockquote>Note: this API is most appropriate when your provider provides multiple artwork via addArtwork or setArtwork with multiple artwork. Avoid using this for artwork like the ‘Featured Art’ source that only have one artwork.</blockquote><p>Muzei 3.4’s ‘My Photos’ source takes advantage of this out of the box, allowing you to add one of your chosen photos to an email you’re composing in Gmail or allow manually backing up your images by copying them to your local storage or Google Drive.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HG0Hflv6cnfEClTIGoO9Iw.png" /><figcaption>The Files app on an Android 10 device showing entries associated with multiple Muzei sources</figcaption></figure><h3>Improved performance</h3><p>One of the most critical user experiences is selecting your source for the first time. Each moment the user needs to wait for that first artwork to appear stretches on forever.</p><p>Just by upgrading your source to <a href="https://github.com/romannurik/muzei/releases/tag/api3.4.0">Muzei API 3.4</a>, you’ll find vastly improved performance when you call setArtwork or addArtwork with a large number of artwork. This can result in changes that used to take 16 seconds to now take less than a second and particularly large data sets of 1000s of images (which used to take 1–2 minutes!) now also take just a second.</p><h3>Better User Onboarding for Sources</h3><p>When building a custom source, the trickiest part is perhaps getting users from installing your app to actually selecting your source. This has often involved giving users instructions to manually follow.</p><p>This is even more critical when running on Android 10 or higher devices, where launchers using the standard <a href="https://developer.android.com/reference/android/content/pm/LauncherApps#getActivityList(java.lang.String,%20android.os.UserHandle)">LauncherApps.getActivityList()</a> API will show an icon for <strong>every</strong> app. While it helps raise awareness that your app is installed, the default behavior of just linking to the system Settings screen for you app (with an Uninstall button) is not particularly useful.</p><p>Muzei API 3.4 adds two new APIs which greatly improve this critical flow:</p><ul><li>The <a href="http://api.muzei.co/reference/com.google.android.apps.muzei.api/-muzei-contract/-sources/is-provider-selected.html">isProviderSelected()</a> API lets you check whether your source has been selected by the user in Muzei.</li><li>The <a href="http://api.muzei.co/reference/com.google.android.apps.muzei.api/-muzei-contract/-sources/create-choose-provider-intent.html">createChooseProviderIntent()</a> API lets you deep link directly into Muzei’s Sources screen, scrolling directly to your source so that user can immediately select it.</li></ul><p>This lets you verify that user has selected your source and, if not, redirect them directly to where they can go to select it.</p><p>It is strongly recommended to catch any ActivityNotFoundException raised when starting the Intent returned by createChooseProviderIntent() and falling back to <a href="http://getLaunchIntentForPackage">getLaunchIntentForPackage()</a> with Muzei’s package name (to support older versions of Muzei that don’t support deep linking to the Sources screen) and a link to Muzei’s Play Store listing (in cases where the user has not installed Muzei at all).</p><p>For a full example, look at <a href="https://www.apkmirror.com/apk/net-ebt/muzei-ghibli/">Muzei Ghibli</a>’s <a href="https://github.com/eboudrant/net.ebt.muzei.miyazaki/blob/develop/app/src/main/java/net/ebt/muzei/miyazaki/RedirectActivity.kt">RedirectActivity</a>.</p><h3>And so much more…</h3><p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei 3.4</a> has plenty of other improvements:</p><ul><li>Muzei 3.4 is the first version translated to other languages! If you’d like to help our crowdsourcing effort in expanding Muzei to support every language, please join our <a href="https://medium.com/u/9a9305faebaa">Crowdin</a> <a href="https://crowdin.com/project/muzei">project</a>.</li><li>If you’re shipping a version of your source that can be installed directly on Wear OS devices, custom commands you publish will now be displayed (using those nice icons you provide via getCommandActions()).</li></ul><p><a href="https://github.com/romannurik/muzei/releases/tag/api3.4.0">Muzei API 3.4</a> also has its own improvements:</p><ul><li>The long deprecated MuzeiArtSource API has been removed entirely.</li><li>The Muzei API has been completely rewritten in Kotlin. You’ll notice a number of reified versions of methods (avoiding that ::class.java mess) and the fact that Artwork is now a immutable class. You can certainly still write your source in the Java programming language, but we’d certainly strongly recommend moving to Kotlin.</li><li>The reference docs at <a href="http://api.muzei.co/">api.muzei.co</a> have been fully updated for Muzei 3.4 with improved examples and cross linking between related documentation.</li></ul><p>Muzei has been a huge passion project for me and I can’t thank <a href="https://medium.com/u/90c74515fd18">Roman Nurik</a> for giving me the opportunity to continue to contribute.</p><p>If you’re looking for examples for upgrading your source to Muzei API 3.4, take a look at pull requests I’ve filed for:</p><ul><li><a href="https://github.com/eboudrant/net.ebt.muzei.miyazaki/commit/e8f9982055efde151823ade751f085aa20b4920e">Muzei Ghibli</a></li><li><a href="https://github.com/arturdryomov/muzei-earth-view/pull/4">Muzei Earth View</a></li><li><a href="https://github.com/jbujalance/Muzplash/pull/2">Muzplash</a></li><li><a href="https://github.com/michaldrabik/muzei-pixelart-android/pull/4">Pixel Art for Muzei</a></li><li><a href="https://github.com/devmil/muzei-bingimageoftheday/pull/10">Muzei Bing Image of the Day</a></li><li><a href="https://github.com/xinthink/muzei-photos/pull/3">Muzei Photos Album</a></li><li><a href="https://github.com/tasomaniac/MuzeiComicsCovers/pull/8">Heroes Comic Covers for Muzei</a></li><li><a href="https://github.com/cmargonis/DistantWorlds-Muzei/pull/4">Distant Worlds for Muzei</a></li></ul><p>If you’d like to help sponsor future development and provide a token of gratitude (and get into Muzei’s private alpha!), you can <a href="https://github.com/sponsors/ianhanniballake">sponsor me on Github</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33fb5c2b6b88" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/muzei-3-4-33fb5c2b6b88">Muzei 3.4</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Muzei Live Wallpaper 3.2.0 Alpha 1]]></title>
            <link>https://medium.com/muzei/muzei-live-wallpaper-3-2-0-alpha-1-41090df5b339?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/41090df5b339</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[muzei]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Mon, 05 Aug 2019 03:00:43 GMT</pubDate>
            <atom:updated>2019-08-28T04:55:28.796Z</atom:updated>
            <content:encoded><![CDATA[<h3>Muzei Live Wallpaper 3.2.0</h3><p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei Live Wallpaper</a> 3.2.0 is now available via the <a href="https://play.google.com/apps/testing/net.nurik.roman.muzei">beta channel</a>!</p><p>As the first release after the <a href="https://developer.android.com/distribute/best-practices/develop/target-sdk">target API 26 requirement</a>, much of the changes here are around ensuring that Muzei and the Muzei API are fully compliant with the latest versions of Android.</p><h3>Legacy Sources</h3><p>Let’s talk about Legacy Sources. Namely, Sources that use the Legacy API, which was fully replaced with the <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 API</a> back in July 2018.</p><p>As per the <a href="https://medium.com/muzei/muzei-3-0-and-legacy-sources-8261979e2264">Legacy Sources blog post</a>, Legacy Sources require targeting API 25 or lower (they literally don’t work if you target API 26 or higher). This also includes Muzei’s side of the Legacy Source API. With the requirement to target API 26 or higher to be on the Google Play Store, <strong>Muzei cannot directly support Legacy Sources anymore</strong>.</p><p>If you install a Legacy Source, Muzei will now send you a notification informing you as such (so you don’t go into Muzei just to not see your newly installed source).</p><p>For a long time, I believed that this restriction meant that users upgrading to Muzei 3.2 would have no recourse at all if they wanted to continue to use a Legacy Source. However, through some significant restructuring, <strong>we now provide a separate </strong><a href="https://github.com/romannurik/muzei/releases/tag/legacy1.0.0"><strong>Muzei Legacy</strong></a><strong> app</strong> that allows you to continue to use Legacy Sources even in Muzei 3.2 or higher.</p><p>Given the pre-existing issues with Legacy Sources, I cannot recommend ever using a Legacy Source, particularly on API 23 and higher devices. You should still send feedback to the apps using the Legacy API to ask them to upgrade to the Muzei 3.0 API.</p><h3>Light theme</h3><p>One of the new features in Android Q is full support for a <a href="https://developer.android.com/preview/features/darktheme">dark theme</a>. Of course, Muzei had a dark theme since the very beginning so, in an ironic twist, Muzei 3.2 is adding a light theme!</p><figure><img alt="Muzei’s Main screen and Sources screen in both dark and light mode" src="https://cdn-images-1.medium.com/max/540/1*Qpc-HqvnvD6EF-oqRE1rQQ.png" /><figcaption>Dark mode vs light mode</figcaption></figure><p>With Muzei’s emphasis on the wallpaper itself, this mostly comes in changing some of the dark UI elements and cards to light, but also affects some more subtle sections (the Auto Advance screen now has a dark background instead of the blue background to avoid large blocks of relatively bright backgrounds, for example).</p><h3>Muzei API 3.2.0</h3><p>Alongside Muzei 3.2.0 is also a new version of the Muzei API, <a href="https://github.com/romannurik/muzei/releases/tag/api3.2.0">3.2.0</a>. This has one important API change for those apps targeting API 29 or higher (replacing openArtworkInfo() with getArtworkInfo() to avoid starting an activity from the background). It also officially deprecates the Legacy API and switches to AndroidX.</p><h3>Try out Muzei 3.2.0 now!</h3><p>Of course, like any new release, we’ve also squashed a ton of bugs, made a lot of reliability improvements, and have tuned a lot small things:</p><ul><li>We’ve updated the Auto Advance icon on the Sources screen to better match its functionality (it now looks like a timer)</li><li>When Muzei is your active wallpaper, going to settings from the Live Wallpaper picker will open the full Muzei experience, allowing you to change your selected Source and other options without finding the app icon in your launcher</li><li>Muzei now supports multi-window mode and has better support for foldable devices</li></ul><p>Check out Muzei 3.2 by joining the <a href="https://play.google.com/apps/testing/net.nurik.roman.muzei">beta channel</a> and downloading Muzei from the Google Play Store.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=41090df5b339" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/muzei-live-wallpaper-3-2-0-alpha-1-41090df5b339">Muzei Live Wallpaper 3.2.0 Alpha 1</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Muzei 3.1.0 for Wear OS]]></title>
            <link>https://medium.com/muzei/muzei-3-1-0-for-wear-os-5ce663e85a56?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ce663e85a56</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[muzei]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Mon, 25 Mar 2019 04:57:57 GMT</pubDate>
            <atom:updated>2019-03-25T04:57:57.828Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei</a> 3.1.0, an update to Muzei’s Wear OS app, is out now!</p><p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei Live Wallpaper - Apps on Google Play</a></p><p>In addition to being a live wallpaper for your Android phone and tablet, Muzei was a launch partner for custom watch faces on Wear OS (née Android Wear) and has continued to take advantage of newer patterns such as <a href="https://medium.com/muzei/muzei-2-5-is-available-now-ded33b16ce52#c4da">complication support</a> and <a href="https://medium.com/muzei/announcing-muzei-live-wallpaper-3-0-d167dd5795a4#65a0">standalone Wear OS devices</a>.</p><h4>Bridging Notifications</h4><p>One important (and for many, the most important part) of their Wear OS device is in “bridging” phone notifications onto their watch, allowing quick actions without getting your phone out. While normally, this is exactly what you want, Muzei runs into an interesting edge case where we felt it important to <a href="https://developer.android.com/training/wearables/notifications/bridger">take control of the bridging behavior</a>.</p><p>Muzei’s Wear OS app supports two different modes:</p><ul><li>Mirroring the current image displayed by Muzei on your phone</li><li>Using another Source, such as the included Featured Art Source, running directly on the watch — no phone app needed at all</li></ul><p>While the first case works fine (you see Muzei’s phone side notification of a new wallpaper on your watch), the later ended up being a source of confusion — you’d see a ‘New wallpaper’ notification, but your watch face continues to show a completely separate wallpaper.</p><p>Therefore in Muzei 3.1.0, Muzei’s Wear OS specifically disables phone notifications from Muzei only if the following two conditions are true:</p><ol><li>You’re actively using Muzei on your watch either via Muzei’s watch face or one of Muzei’s complications on another active watch face</li><li>You’re using a different source on your watch other than the ‘From phone’ Source</li></ol><p>As soon as you change either of these conditions, the next notification posted by Muzei’s phone app will appear on your watch (unfortunately, the bridging APIs don’t automatically update the state of all current notifications).</p><h4>Bug Fixes and Performance Improvements</h4><p>Like most apps, we’re continuing to improve with each release and this release is no different.</p><p>We’ve fixed a bug that prevented you from using <a href="https://developer.android.com/training/wearables/ui/rotary-input">rotary input</a> (i.e., rotating the side button) when you open Muzei’s Wear OS app. Should make it just a bit easier to interact with the app, be it to view the current artwork’s details or change your selected Source.</p><p>We’ve also done a full pass on reliability, updating to the recent stable version of <a href="https://developer.android.com/topic/libraries/architecture/workmanager">WorkManager</a>, ensuring that loading images (be it from your phone or otherwise) works consistently every time.</p><p>In addition, if you’re a different Source on your watch, such as <a href="https://www.apkmirror.com/apk/net-ebt/muzei-ghibli-wear-os/muzei-ghibli-wear-os-3-0-release/muzei-ghibli-wear-os-3-0-android-apk-download/">Muzei Ghibli</a>, you’ll now notice that Muzei shows a link to the Source’s Settings screen — the same will be true for any Source that has a Settings screen.</p><h4>Wear OS improvements are here, more to come</h4><p>While this Muzei 3.1.0 is specifically for Wear OS, there’s more improvements for Muzei in the works for both the phone and Wear OS app. Make sure to follow the <a href="https://medium.com/muzei">Muzei publication</a> for updates!</p><p><a href="https://medium.com/muzei">muzei</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ce663e85a56" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/muzei-3-1-0-for-wear-os-5ce663e85a56">Muzei 3.1.0 for Wear OS</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Announcing Muzei Live Wallpaper 3.0]]></title>
            <link>https://medium.com/muzei/announcing-muzei-live-wallpaper-3-0-d167dd5795a4?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/d167dd5795a4</guid>
            <category><![CDATA[muzei]]></category>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[android]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Mon, 15 Oct 2018 15:01:01 GMT</pubDate>
            <atom:updated>2018-10-15T15:01:01.748Z</atom:updated>
            <content:encoded><![CDATA[<p>Muzei 3.0 is the latest release of <a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei Live Wallpaper</a>, now on Google Play.</p><p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei Live Wallpaper - Apps on Google Play</a></p><p>This is a culmination of over a year and half of work and comes with significant new features, a visual refresh, and a <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">brand new API</a> that greatly improves reliability.</p><h3>What’s New</h3><h4>A brand new plugin API</h4><p>With <a href="https://medium.com/muzei/the-muzei-plugin-api-and-androids-evolution-9b9979265cfb">significant changes to Android</a> since Muzei’s release back when Android 4.4 KitKat was the latest version of Android, a complete rewrite was needed for Muzei’s API to ensure that it was compatible with <a href="https://developer.android.com/training/monitoring-device-state/doze-standby">Doze and App Standby</a> as well as <a href="https://developer.android.com/about/versions/oreo/background">Background Execution Limits</a>.</p><p>The result is a <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">new, modern API</a> that works great on all versions of Android that is much, much more reliable than the previous API. By preloading the next wallpaper ahead of time, <strong>hitting the Next button in Muzei will instantly start the transition to the next wallpaper</strong>, even if you’re offline.</p><p>The new API is also fully compatible with the <a href="https://support.google.com/googleplay/android-developer/answer/113469#targetsdk">Google Play Store requirement to target API 26 or higher</a>.</p><h4>Auto Advance</h4><p>When it comes to large, often 4k+ wallpaper images, ensuring that Muzei isn’t burning through data is incredibly important. <strong>Muzei 3.0 introduces a new set of centralized settings called Auto Advance</strong>. These new settings give users control over how often their wallpaper should change and whether Muzei should delay loading new wallpapers until you are Wi-Fi.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*Dufc3zSLzlkwsTmp5G128g.png" /><figcaption>Auto Advance settings float above the Sources screen</figcaption></figure><p>Being built directly into Muzei, Auto Advance applies to all sources built with the new API and removes the need for each source developer from building these same settings themselves.</p><h4>Improved integration with Tasker</h4><p><a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm">Tasker</a> is still one of the most flexible methods for customizing your phone and was a natural fit for Muzei. While previous versions of Muzei only allowed you to trigger the ‘Next’ action from Tasker, <strong>Muzei 3.0 adds the ability to change to any source from Tasker</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aDbcjvhow8TeKoNR48Ctlg.png" /><figcaption>Change your selected Source from Tasker</figcaption></figure><p>Now when you select Muzei from Tasker’s Plugin screen, you’ll be given the option to choose between all of the Sources built with the new API on your device as the action to trigger.</p><h4>Improved control over effects</h4><p>Since <a href="https://medium.com/google-developers/serendipitous-ideas-3a1721a6f716">the beginning</a>, Muzei has focused on making a background that fits perfectly with your home screen by offering blurring, dimming, and reduced saturation effects to apply to the wallpapers available through Muzei.</p><p>With Muzei 3.0, users now have complete control over what effects they apply on not only the home screen, but <strong>also on the lock screen</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*utl6R-1VKA2_I9Gg3TjmDA.gif" /><figcaption>Previewing the effects on your home screen and lock screen</figcaption></figure><p>So if you’re after a blurry lock screen background or a crystal clear greyscale home screen background, you’ll be able to customize Muzei exactly how you want it.</p><h4>Gesture Controls</h4><p>Being a live wallpaper, Muzei can receive tap events when interacting with your home screen. Expanding on the previous option to double tap to temporarily disable the effects, <strong>Muzei now supports three finger tap gestures and customizing the action</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NR-NNW-8Ih09Ga5N49IL7w.png" /><figcaption>Improved gesture controls allow you to set exactly what each gesture does</figcaption></figure><p>This allows you to customize Muzei and avoid conflicting gestures (such as with devices that use double tap to turn the screen off) while still avoiding accidental triggering of the actions when interacting with the icons or widgets on the home screen.</p><h4>A standalone Wear OS experience</h4><p>Muzei was a launch partner for watch faces on Android Wear and has continued to improve the experience since then, including <a href="https://medium.com/muzei/muzei-2-5-is-available-now-ded33b16ce52#c4da">support for watch face complications</a>, allowing you to use your Muzei wallpaper as the background for Muzei’s watch face as well as many other watch faces.</p><p>With Muzei 3.0, <strong>you can use Muzei on your Wear OS device without using Muzei on your phone</strong>. In addition to the existing support for mirroring your Muzei wallpaper from your phone, Muzei on Wear OS can also use the ‘Featured art’ source directly. One additional side effect of this change is that <strong>you can now use Muzei for Wear OS even if you have an iOS device</strong>.</p><p>Third party plugin developers can also publish their sources for Wear OS and they’ll show up alongside the ‘Featured art’ and ‘From phone’ Source.</p><h3>What’s Next</h3><p><strong>Muzei 3.0 is out now</strong> and can be downloaded <a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">from Google Play</a>. You can get early access to future versions of Muzei by <a href="https://play.google.com/apps/testing/net.nurik.roman.muzei">joining the open beta program</a>.</p><p>The Muzei 3.0 API is <a href="https://github.com/romannurik/muzei/releases/tag/api3.0.0">final</a> and available to all developers to integrate into their app. I’d strongly recommend joining the <a href="https://plus.google.com/communities/114043146173681149763">Muzei Google+ community</a> as the best place to ask questions about Muzei and about the Muzei API (you’ll also get alpha access to early builds of Muzei if you join that community <em>and then</em> join the testing program at the above link).</p><p>Many of the existing sources on the Play Store are still using the previous Muzei 2.x Legacy API. These <a href="https://medium.com/muzei/muzei-3-0-and-legacy-sources-8261979e2264">Legacy Sources</a> are still partially supported in Muzei 3.0, but given their complete incompatibility with the <a href="https://support.google.com/googleplay/android-developer/answer/113469#targetsdk">target API 26 requirement</a>, support for Legacy Sources will be removed in a future version of Muzei on API 23+ devices. <strong>Please, please send feedback to Legacy Sources</strong> asking them to convert to the Muzei 3.0 API. The <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 API blog post</a> goes through the new API in detail and they can always contact us at <a href="mailto:support@muzei.co">support@muzei.co</a> with any questions.</p><p>I’m extremely excited about this Muzei release, but even more excited about the further improvements planned for future versions. Follow along with all of the announcements on our Medium publication at <a href="https://medium.com/muzei">medium.com/muzei</a>.</p><p><a href="https://medium.com/muzei">muzei</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bUMqFOl0sErw6nwa5oOLtQ.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d167dd5795a4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/announcing-muzei-live-wallpaper-3-0-d167dd5795a4">Announcing Muzei Live Wallpaper 3.0</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Muzei 3.0 and Legacy Sources]]></title>
            <link>https://medium.com/muzei/muzei-3-0-and-legacy-sources-8261979e2264?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/8261979e2264</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[muzei]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 10 Oct 2018 04:39:01 GMT</pubDate>
            <atom:updated>2019-08-05T00:01:44.168Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><strong>TL/DR</strong>: If you’re using Muzei 3.2 or higher and want to continue to use a Legacy Source until they convert to the <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 API</a>, you can install <a href="https://github.com/romannurik/muzei/releases/tag/legacy1.0.0"><strong>Muzei Legacy</strong></a> to regain support for Legacy Sources.</blockquote><p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei</a> offers a plugin system that allows any app to provide wallpapers for display in Muzei. Muzei 3.0 introduced a <a href="https://medium.com/@ianhlake/muzei-3-0-and-the-new-api-4fd3d6133db6">new API</a> that fixes a number of long standing issues, particularly on API 23+ and the battery optimizations changes introduced on those newer versions of Android.</p><p>Apps built with the previous Muzei 2.x Legacy API are classified as ‘Legacy Sources’ in Muzei 3.0 <strong>and are no longer directly supported in Muzei 3.2 and higher.</strong></p><h3>What’s different about Legacy Sources?</h3><h4>No support for Auto Advance</h4><p>Legacy Sources load new wallpapers in two steps:</p><ol><li>Based on their own internal logic, they ‘publish’ new wallpapers to Muzei with a URL to load the wallpaper from.</li><li>Muzei then loads the new wallpaper immediately.</li></ol><p>This means that the decision on when to load new wallpapers depend entirely on the Legacy Source. This means that <strong>Legacy Sources don’t support </strong><a href="https://medium.com/@ianhlake/muzei-3-0-and-the-new-api-4fd3d6133db6#1850"><strong>Auto Advance</strong></a>.</p><p>Therefore it is not possible to control how often Legacy Sources load new wallpapers or if they wait until the device is on WiFi unless the Legacy Source has built those specific settings.</p><p>It also means that data usage caused by downloading wallpapers is attributed to Muzei itself (since it is Muzei that downloads the wallpaper on the Legacy Source’s request), rather than attributed to the Legacy Source itself. This is in contrast to Muzei 3.0 Sources which have their data usage properly attributed to the Source, not to Muzei.</p><h4>Requirement to disable Battery Optimizations on API 23+ devices</h4><p>Due to how to the Legacy API was built and <a href="https://medium.com/@ianhlake/the-muzei-plugin-api-and-androids-evolution-9b9979265cfb">Android’s evolution</a>, <strong>you must manually disable Battery Optimizations on Legacy Sources</strong> on API 23 (Marshmallow, Android 6.0) and higher devices.</p><blockquote><strong>Note</strong>: you do <strong>not</strong> need to disable Battery Optimizations on Muzei itself — only on the third party app providing the Legacy Source you want to use.</blockquote><p>This is generally done through the Settings app on your device. However, the location for these settings can differ by manufacturer. Generally, Google searching for ‘YOUR DEVICE NAME battery optimizations’ will bring up relevant results for disabling battery optimizations.</p><h3>What’s the future for Legacy Sources?</h3><p>In late 2018, Google Play is enforcing that <a href="https://developer.android.com/distribute/best-practices/develop/target-sdk">all new apps and updated apps target API 26 or higher</a>. The Legacy API is totally incompatible with apps that target API 26 or higher, meaning that all new plugins and apps still publishing updates <strong>must</strong> move to the <a href="https://medium.com/@ianhlake/muzei-3-0-and-the-new-api-4fd3d6133db6#1850">Muzei 3.0 API</a>.</p><blockquote><strong>Note</strong>: Muzei has disallowed apps that target API 26+ from using the Legacy API even on Muzei 2.6.0 builds for exactly this reason.</blockquote><p>Please send feedback to any apps providing a Legacy Source with a link to the Muzei 3.0 API.</p><p><a href="https://medium.com/@ianhlake/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 and the new API</a></p><p>You can also tell them that they can email <a href="mailto:support@muzei.co">support@muzei.co</a> for help moving to the new API (including help writing their new API support if the app is open source!).</p><h3>What happens to Legacy Sources that aren’t updated?</h3><p>To continue to push updates to Muzei after November 2018, Muzei itself must target API 26 or higher, which means that <strong>Muzei 3.2 removes direct support for Legacy Sources</strong>.</p><p>However, to avoid cases where users upgrading lose permanent access to Legacy Sources, we have made <a href="https://github.com/romannurik/muzei/releases/tag/legacy1.0.0"><strong>Muzei Legacy</strong></a> available as an additional installation (not through the Google Play Store, given that it needs to target API 25) to re-add support for Legacy Sources to Muzei 3.2 or higher.</p><p>Given all of the above issues with Legacy Sources, it is still strongly recommended to avoid using Legacy Sources, particularly on API 23 and higher devices. Please send feedback to Sources continuing to use the Legacy API to have them convert to the <a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 API</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bUMqFOl0sErw6nwa5oOLtQ.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8261979e2264" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/muzei-3-0-and-legacy-sources-8261979e2264">Muzei 3.0 and Legacy Sources</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Muzei 3.0 and the new API]]></title>
            <link>https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6?source=rss-51a4f24f5367------2</link>
            <guid isPermaLink="false">https://medium.com/p/4fd3d6133db6</guid>
            <category><![CDATA[android-app-development]]></category>
            <category><![CDATA[muzei]]></category>
            <dc:creator><![CDATA[Ian Lake]]></dc:creator>
            <pubDate>Wed, 11 Jul 2018 17:09:01 GMT</pubDate>
            <atom:updated>2018-10-03T04:43:10.933Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://play.google.com/store/apps/details?id=net.nurik.roman.muzei">Muzei</a>, besides being a capable live wallpaper in its own right, has continued to be successful due to its plugin architecture which allowed many, many other developers to build their own wallpaper source, ensuring that users always have a wide variety of images from whatever source they could imagine.</p><p>As previously discussed in my <a href="https://medium.com/@ianhlake/the-muzei-plugin-api-and-androids-evolution-9b9979265cfb"><em>The Muzei Plugin API and Android’s Evolution</em></a> post, a lot has changed in Android since Muzei was first released by <a href="https://medium.com/u/90c74515fd18">Roman Nurik</a> back in the times of Android 4.4.</p><p>This has meant a full rewrite of both Muzei and its API has been necessary to ensure that it is compatible with <a href="https://developer.android.com/training/monitoring-device-state/doze-standby">Doze and App Standby</a> as well as <a href="https://developer.android.com/about/versions/oreo/background">Background Execution Limits</a>.</p><p>I’m happy to announce that alongside the first alpha of Muzei 3.0, <strong>an alpha of the new Muzei API is available</strong> that brings the API in alignment with the latest best practices and offers some significant improvements to the overall user experience for Muzei.</p><blockquote><strong>Edit</strong>: the Muzei 3.0 API is now available <a href="https://plus.google.com/+IanLake/posts/6UuMM7qHmoK">in beta</a> and is expected to be forward compatible with Muzei 3.0 as we move towards a full production release.</blockquote><h3>No more unresponsive Next buttons</h3><p>Rewriting an entire app and plugin ecosystem is not something to be taken lightly so any new API needed to offer a significantly better user experience, both to end users and to developers.</p><p>One of the main complaints of Muzei is that you’d hit the ‘Next’ button in the app and nothing would happen. That’s a bad user experience in all regards that the new API seeks to remove entirely even as a possibility.</p><h4>Supporting multiple artwork</h4><p>Unlike the previous MuzeiArtSource based API that had just a single publishArtwork method and a single current artwork, the new API is now based on a MuzeiArtProvider class — a special subclass of ContentProvider.</p><p>This new structure allows your MuzeiArtProvider to add multiple artwork at once. For example, if your network call to your backend returns 30 images, you no longer need to randomly pick one and throw the other 29 away. Instead, you can call addArtwork 30 times and Muzei will automatically go through all 30 in the order you add them before asking you to load more artwork. Besides significantly lowering the amount of requests to your backend, this allows Muzei’s UI to only show the Next button if you actually have more artwork ready.</p><p>This fundamental change also means <strong>the entry point for your </strong><strong>MuzeiArtProvider is significantly changed</strong>. Rather than a call to onUpdate or onTryUpdate every time the wallpaper changes, you now get a callback to onLoadRequested only when Muzei loads the last new image. This should be your cue to load more artwork!</p><blockquote>Note: not every source has an endless backend of new artwork. Don’t worry about it. Even if you don’t load new artwork in response to onLoadRequested, Muzei will loop back through the rest of the artwork you have, loading older artwork randomly (while avoiding selecting recently shown artwork) without you having to do any extra work.</blockquote><h4>Pre-loading artwork</h4><p>The time between hitting the Next button and the next wallpaper displaying is the absolutely most nerve racking time for users — it has to be quick and reliable every time.</p><p>With this in mind, Muzei will now pre-load the next artwork before it is displayed, ensuring there is already a locally cached copy available before the user can even hit the Next button. This ensures that even if the user is offline, if they can see a Next button, it will go to a new wallpaper. It also means transitions are near instantaneous, even on slow internet connections!</p><h3>Downloading the artwork itself</h3><p>Another significant change with the new API is that <strong>downloading artwork happens on the plugin side</strong>. Instead of Muzei using the call to publishArtwork as a sign to kick off its own download process, the downloading now happens via a call to your MuzeiArtProvider’s openFile method, which returns an InputStream to your image.</p><p>The default implementation of openFile handles much of the same cases as the previous API such as content:// or android.resource:// Uris as well as basic, unauthenticated http:// or https:// Uris.</p><blockquote>Note: Muzei enforced TLS support on Android platform versions that required it to specifically be enabled. You now need to do this work yourself. An example of this can be found in the <a href="https://github.com/romannurik/muzei/blob/v3.0.0-alpha1/main/src/main/java/com/google/android/apps/muzei/sources/SourceArtProvider.kt#L145">SourceArtProvider</a>.</blockquote><p>Since openFile is being called within your plugin’s process, there’s no longer any need to use a FileProvider or similar solution for loading local images that you have access to — a regular old file:// URL generated from Uri.fromFile actually works fine.</p><p>This flexibility also opens up the possibility to download images from authenticated servers or support custom protocols outside of http and https all completely transparently to Muzei itself.</p><h3>When Muzei updates</h3><p>One consistent feature request we’ve gotten for Muzei is better control over when Muzei transitions to the next wallpaper. Different plugins would add different settings on when to publish new artwork or whether to only update when you are on Wi-Fi.</p><p>With the new API, <strong>the settings controlling when wallpaper is loaded have been centralized within Muzei through a new set of options called Auto Advance</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Dufc3zSLzlkwsTmp5G128g.png" /><figcaption>Auto Advance settings float above the Sources screen</figcaption></figure><p>For sources built on the MuzeiArtProvider API, users will be able to configure at what interval they want to automatically advance to the next artwork and whether that should only happen over Wi-Fi. Individual plugins no longer need to specify or control these settings themselves — as long as you provide artwork when onLoadRequested is called, Muzei will handle everything else.</p><h3>Implementing onLoadRequested</h3><p>We’ve talked about how important onLoadRequested is, so it deserves some extra attention to explain exactly how to implement it. We’ll be using the <a href="https://github.com/romannurik/muzei/tree/v3.0.0-alpha1/example-unsplash">example-unsplash</a> project as our example project. It implements a straightforward plugin that downloads the latest popular photos from <a href="https://unsplash.com/">Unsplash</a>.</p><p>Our <a href="https://github.com/romannurik/muzei/blob/v3.0.0-alpha1/example-unsplash/src/main/java/com/example/muzei/unsplash/UnsplashExampleArtProvider.kt">UnsplashExampleMuzeiArtProvider</a>&#39;s onLoadRequested looks like:</p><pre>override fun onLoadRequested(initial: Boolean) {<br>  UnsplashExampleWorker.enqueueLoad()<br>}</pre><p>Yep, that’s it. While onLoadRequested is called on a background thread (so it is safe to do database or shared preference calls), <strong>it is strongly, strongly recommended to push the actual loading to </strong><a href="https://developer.android.com/topic/libraries/architecture/workmanager"><strong>WorkManager</strong></a>, JobScheduler, or JobIntentService.</p><p>Personally, I strongly prefer WorkManager for this kind of work since it will start execution immediately if conditions are met (say, you’re connected to the internet) and offers control over retrying. Muzei has been using it in production for all of the source loading as of Muzei 2.6.0.</p><p>The <a href="https://github.com/romannurik/muzei/blob/v3.0.0-alpha1/example-unsplash/src/main/java/com/example/muzei/unsplash/UnsplashExampleWorker.kt">UnsplashExampleWorker</a> is no more complicated. Its core doWork method looks like:</p><pre>override fun doWork(): Result {<br>  val photos = try {<br>    UnsplashService.popularPhotos()<br>  } catch (e: IOException) {<br>    Log.w(TAG, &quot;Error reading Unsplash response&quot;, e)<br>    return Result.RETRY<br>  }<br>  if (photos.isEmpty()) {<br>    Log.w(TAG, &quot;No photos returned from API.&quot;)<br>    return Result.FAILURE<br>  }<br>  val attributionString =<br>      applicationContext.getString(R.string.attribution)<br>  photos.map { photo -&gt;<br>    Artwork().apply {<br>      token = photo.id<br>      title = photo.description ?: attributionString<br>      byline = photo.user.name<br>      attribution = <br>          if (photo.description != null) attributionString else null<br>      persistentUri = photo.urls.full.toUri()<br>      webUri = photo.links.webUri<br>      metadata = photo.user.links.webUri.toString()<br>    }<br>  }.forEach { artwork -&gt;<br>    ProviderContract.Artwork.addArtwork(applicationContext,<br>        UnsplashExampleArtProvider::class.java,<br>        artwork)<br>  }<br>  return Result.SUCCESS<br>}</pre><p>We load from a Retrofit service, create a new Artwork object for each photo returned from the API, then call ProviderContract.Artwork.addArtwork to add the artwork to the correct MuzeiArtProvider.</p><p>Unlike the previous API where all calls to publishArtwork needed to be within the MuzeiArtSource itself (forcing you to start the service, and your own action handling in onStartCommand, etc), <strong>the </strong><strong>ProviderContract.Artwork class has static methods for common operations</strong>, such as getting the last artwork or adding new artwork, <strong>that can be called from anywhere in your application</strong>.</p><p>As a convenience, each of the static methods in ProviderContract.Artwork also has a non-static equivalent in MuzeiArtProvider, so calling addArtwork within the MuzeiArtProvider is as straightforward as it gets.</p><blockquote>Note: as a MuzeiArtProvider is a ContentProvider itself, you can also use all of the ContentResolver based APIs such as query and delete using the base content URI returned by ProviderContract.Artwork.getContentUri and the column names in ProviderContract.Artwork .</blockquote><h3>Adding custom commands</h3><p>Muzei’s API has always offered the ability to add custom commands to your plugin, giving users additional plugin specific actions they can take.</p><p>As the concept of the ‘Next Artwork’ is now controlled entirely by the existence of more valid artwork being added to your MuzeiArtProvider, the commands you return from the getCommands method should now all be custom commands (the default behavior is to return an empty list).</p><p>The <a href="https://github.com/romannurik/muzei/blob/v3.0.0-alpha1/example-unsplash/src/main/java/com/example/muzei/unsplash/UnsplashExampleArtProvider.kt#L41">Unsplash Example</a> returns two custom commands:</p><pre>override fun getCommands(artwork: Artwork) = listOf(<br>    UserCommand(COMMAND_ID_VIEW_PROFILE,<br>        context.getString(R.string.action_view_profile, <br>            artwork.byline)),<br>    UserCommand(COMMAND_ID_VISIT_UNSPLASH,<br>        context.getString(R.string.action_visit_unsplash)))</pre><p>You’ll note that we’re given the Artwork object that these commands should be associated with. This allows you to customize what commands are supported on an artwork by artwork basis or, like this example, customize the strings used for your actions with information from your artwork.</p><p>Handling commands is then done from the onCommand method, which also gets the Artwork object and the id of the action that was selected.</p><p>The <a href="https://github.com/romannurik/muzei/blob/v3.0.0-alpha1/example-unsplash/src/main/java/com/example/muzei/unsplash/UnsplashExampleArtProvider.kt#L47">Unsplash Example</a> implements this with a simple when statement:</p><pre>override fun onCommand(artwork: Artwork, id: Int) {<br>  when (id) {<br>    COMMAND_ID_VIEW_PROFILE -&gt; {<br>       val profileUri = artwork.metadata?.toUri() ?: return<br>       context.startActivity(Intent(Intent.ACTION_VIEW, profileUri))<br>    }<br>    COMMAND_ID_VISIT_UNSPLASH -&gt; {<br>      val unsplashUri = context.getString(R.string.unsplash_link) +<br>          ATTRIBUTION_QUERY_PARAMETERS<br>      context.startActivity(Intent(Intent.ACTION_VIEW,<br>          unsplashUri.toUri()))<br>    }<br>  }<br>}</pre><p>You’ll note that here we use the metadata property. This String property is not used by Muzei in any way so you can repurpose it for whatever extra information you want to attach to each Artwork. In this case, we attach the URL of the photographer’s profile.</p><h3>Adding a MuzeiArtProvider to your Manifest</h3><p>Just like any other ContentProvider or service, you must add your MuzeiArtProvider to your manifest:</p><pre>&lt;provider<br>  android:name=&quot;com.example.CoolArtworkMuzeiArtProvider&quot;<br>  android:authorities=&quot;com.example.coolartwork&quot;<br>  android:label=&quot;@string/name&quot;<br>  android:description=&quot;@string/description&quot;<br>  android:exported=&quot;true&quot;<br>  android:permission=<br>    &quot;com.google.android.apps.muzei.api.ACCESS_PROVIDER&quot;&gt;<br>  &lt;intent-filter&gt;<br>    &lt;action android:name=&quot;com.google.android.apps.muzei.api.MuzeiArtProvider&quot;/&gt;<br>  &lt;/intent-filter&gt;<br>&lt;/provider&gt;</pre><p>You’ll that it is strongly recommended to add the android:permission to your MuzeiArtProvider. This ensures that only your app and Muzei can read your Artwork. (Of course, you could leave it off if you want any app to be able to read your Artwork — your choice.)</p><p>Similar to a MuzeiArtSource, you can use &lt;metadata&gt; tags for a settingsActivity and setupActivity . More information on this can be found in the documentation.</p><h3>Migrating from MuzeiArtSource</h3><p>For users using Muzei 3.0, sources using MuzeiArtSource will appear in a separate ‘Legacy Sources’ option within Muzei’s ‘Sources’ screen, clearly delineating them from the modern sources built using MuzeiArtProvider. This is absolutely an intentional change.</p><h4>If your app targets API 26 or higher</h4><p>With the <a href="https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html">Target API level requirement for late 2018</a> enforcing a targetSdkVersion of 26 or higher for all updates to existing apps starting in November 2018, you might be considering changing your targetSdkVersion now.</p><p>If you update your targetSdkVersion now, note that the MuzeiArtSource API will no longer work. At all. Muzei has been enforcing this for quite some time, making your source unselectable.</p><p>In this case, you can safely delete your MuzeiArtSource and add a new MuzeiArtProvider .</p><h4>Supporting both the old and new API simultaneously</h4><p>To give the best experience for Muzei users in the short term, it is strongly recommended to support both the old and new API simultaneously by keeping your MuzeiArtSource functional while also providing a MuzeiArtProvider implementation for users already on Muzei 3.0.</p><p>In order to avoid duplicate sources in Muzei’s UI and to automatically migrate users of your existing MuzeiArtSource to your new MuzeiArtProvider, you should add a replacement metadata element to your MuzeiArtSource :</p><pre>&lt;service<br>  android:name=&quot;com.example.muzei.unsplash.UnsplashExampleArtSource&quot;<br>  android:label=&quot;@string/name&quot;<br>  android:description=&quot;@string/description&quot;<br>  android:icon=&quot;@drawable/ic_source&quot;<br>  tools:ignore=&quot;ExportedService&quot;&gt;<br>  &lt;intent-filter&gt;<br>    &lt;action android:name=&quot;com.google.android.apps.muzei.api.MuzeiArtSource&quot;/&gt;<br>   &lt;/intent-filter&gt;<br>   &lt;meta-data<br>     android:name=&quot;color&quot;<br>     android:value=&quot;@color/colorPrimary&quot;/&gt;<br>   <strong>&lt;meta-data<br>     android:name=&quot;replacement&quot;<br>     android:value=&quot;com.example.muzei.exampleunsplash&quot;/&gt;</strong><br>&lt;/service&gt;</pre><p>Muzei will read that metadata attribute and ensure that only your MuzeiArtProvider is visible in the Muzei UI and any users are automatically migrated over to the MuzeiArtProvider with the given authority.</p><h4>Timeline for Muzei 3.0 and beyond</h4><p>Muzei 3.0 is in <a href="https://plus.google.com/+IanLake/posts/6ediP6Tfwc8">beta</a> right now and it will have a longer alpha / beta period to give you developers a chance to exercise and give feedback on the new API. It will then to go production and all users will have access to sources built on the new API.</p><p>Before the November 2018 requirement to target API 26+, another Muzei release will be made, warning users that still have a legacy source selected that their legacy source will stop working on ~January 2019.</p><p>In ~January 2019, Muzei itself will upgrade to target API 26+ and all support for MuzeiArtSource built plugins will be dropped (it literally won’t work at all).</p><h3>Get started today</h3><blockquote><strong>Update:</strong> Muzei 3.0 is now available in the <a href="https://play.google.com/apps/testing/net.nurik.roman.muzei">open beta</a>!</blockquote><p>Muzei 3.0 is available in alpha today. To join the alpha:</p><ol><li>Join the <a href="https://plus.google.com/communities/114043146173681149763">Muzei Google+ Community</a></li><li><a href="https://play.google.com/apps/testing/net.nurik.roman.muzei">Opt in to alpha testing</a> (you may need to leave the tester program and rejoin if you previously were beta testing Muzei)</li><li>After ~15 minutes, you should see an update available via the Google Play Store</li></ol><p>If you’d like to get started with the new API, update your dependency:</p><pre>implementation &quot;com.google.android.apps.muzei:muzei-api:3.0.0-beta02&quot;</pre><p>You can download the aar, sources.jar, or javadoc.jar from the <a href="https://github.com/romannurik/muzei/releases/tag/v3.0.0-alpha1">Github release page</a>.</p><blockquote>Note: The Muzei 3.0 API is in beta and subject to minor bug fixes. The API as written now will be forward compatible with Muzei 3.0 releases and is safe to use in production.</blockquote><p><strong>Feedback is greatly appreciated</strong>. The best way to contact us through the <a href="https://plus.google.com/communities/114043146173681149763">Google+ community</a> or by sending an email to <a href="mailto:support@muzei.co">support@muzei.co</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bUMqFOl0sErw6nwa5oOLtQ.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4fd3d6133db6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/muzei/muzei-3-0-and-the-new-api-4fd3d6133db6">Muzei 3.0 and the new API</a> was originally published in <a href="https://medium.com/muzei">muzei</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>