<?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"><channel><title><![CDATA[Nikl's Blog]]></title><description><![CDATA[Personal website with programming projects and related blog posts]]></description><link>https://www.nikl.me</link><generator>GatsbyJS</generator><lastBuildDate>Sun, 26 Jul 2026 20:31:56 GMT</lastBuildDate><item><title><![CDATA[Bevy ECS as a data layer for static site generation with Leptos]]></title><description><![CDATA[TLDR: I wrote a static site generator using Leptos and Bevy ECS called Cinnog. There are many static site generators and since I tend to use…]]></description><link>https://www.nikl.me/blog/2024/bevy_ecs_as_data_layer_in_leptos_ssg/</link><guid isPermaLink="false">https://www.nikl.me/blog/2024/bevy_ecs_as_data_layer_in_leptos_ssg/</guid><pubDate>Sat, 06 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;TLDR: I wrote a static site generator using Leptos and Bevy ECS called &lt;a href=&quot;https://crates.io/crates/cinnog&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Cinnog&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are many static site generators and since I tend to use this website as a playground for learning new technologies, it has used a couple of different ones in the past. The current version is built using Gatsby which is in part because Gatsby uses React (which I wanted to learn at the time) and because I like their data layer.&lt;/p&gt;
&lt;p&gt;A data layer in a static site generator simplifies collecting and preparing data required to build the website. For example, a user might get their blog posts from a headless CMS and generate different resolutions for image assets. When deciding what routes to generate, they can look into the data layer to see what blog posts there are and then generate a route for every post. The just-generated smaller version of a post&apos;s image could be displayed on the list of blog posts while the post itself uses the original version.&lt;/p&gt;
&lt;p&gt;Many generators support a given structure of data and asset files but leave more complex data loading and preparation to their users. I think it is very helpful when a generator provides the structure and access for collecting, preparing and using any kind of data needed for building a website&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. This is especially the case if there is a good API for third-party plugins to hook into.&lt;/p&gt;
&lt;p&gt;So... I have some time during the holidays... How about trying to build a static site generator with Leptos using Bevy ECS as the data layer?&lt;/p&gt;
&lt;h2 id=&quot;static-site-generation-with-leptos&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#static-site-generation-with-leptos&quot; aria-label=&quot;static site generation with leptos permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Static site generation with Leptos&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://leptos.dev/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Leptos&lt;/a&gt; is an open source Rust framework for building websites. It supports both &lt;a href=&quot;https://book.leptos.dev/getting_started/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;client-side rendering and server-side rendering&lt;/a&gt; utilizing &lt;a href=&quot;https://webassembly.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;WebAssembly&lt;/a&gt; (WASM) for reactivity in the browser. By default, the client-side rendered approach is a single page application, where the &lt;code class=&quot;language-text&quot;&gt;index.html&lt;/code&gt; loads a WASM binary that will render the website and handle navigation client-side. Every component that you write will be rendered out of the same WASM binary in the browser. Leptos uses an &lt;a href=&quot;https://book.leptos.dev/reactivity/14_create_effect.html#autotracking-and-dynamic-dependencies&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;interesting setup&lt;/a&gt; for fine-grained reactivity that performs very well.&lt;/p&gt;
&lt;p&gt;Since version 0.5, Leptos supports basic static site generation (&lt;a href=&quot;https://github.com/leptos-rs/leptos/releases/tag/v0.5.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;see the release notes on &quot;Static Site Generation&quot;&lt;/a&gt;). It can be used to pre-generate some routes in a server-side-rendered application for improved performance. If you restrict your whole project to static routes, you can generate the complete website with multiple pages.&lt;/p&gt;
&lt;p&gt;In the same update, Leptos also released an experimental feature called &quot;islands&quot; (&lt;a href=&quot;https://github.com/leptos-rs/leptos/releases/tag/v0.5.0&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;see the release notes on Islands&lt;/a&gt;). When using islands, &quot;normal&quot; Leptos components are static and served as HTML instead of being rendered client-side through WASM. Only islands will use WebAssembly for reactivity in the browser.&lt;/p&gt;
&lt;p&gt;I think the combination of islands and static site generation is perfect for a website like mine. Leptos is the first web framework in Rust that seriously tempts me to rewrite this website (again). If you are interested in web development with Rust, &lt;a href=&quot;https://book.leptos.dev/getting_started/index.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;check it out&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;What has been keeping me from rewriting this blog using Leptos, is that I use Gatsby&apos;s GraphQL data layer to do a bunch of stuff that is not supported in Leptos itself. I could do things like reading all my markdown files and converting them to HTML as part of the Leptos app, but it feels like there should be a general data layer that one can fill and manipulate before reading it from the Leptos components. Things like automatically creating anchors for headers or generating an RSS feed from all blog posts are easier with a data layer.&lt;/p&gt;
&lt;h2 id=&quot;bevy-ecs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bevy-ecs&quot; aria-label=&quot;bevy ecs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bevy ECS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt; is an open source Rust game engine built with the Entity Component System (ECS) pattern to separate data from behaviour. Using ECS involves breaking up your program into entities, components, and systems. Entities are identifiers that are assigned groups of components. Systems process entities with given sets of components&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In Bevy ECS, components are simple rust structs and systems are &quot;normal&quot; functions. A system requests the data it needs through its parameters, which feels a bit like dependency injection. One can think about an ECS as an in-memory database where systems are the queries.&lt;/p&gt;
&lt;p&gt;Many reasons for choosing an ECS for game development are not relevant for a static site generator. And honestly, I am not yet convinced that this is a sane idea at all. But the API of Bevy ECS is nice to work with. Writing rust functions that automatically get the data they ask for feels close to perfect for querying a data layer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bevy and Leptos call different things &quot;component&quot;. In Leptos, a component is a building block for the website (like a React component). While in Bevy, components are data and live in the ECS World. In this post, I try to write &quot;Bevy component&quot; and &quot;Leptos component&quot; to differentiate between the two.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;what-could-it-look-like&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-could-it-look-like&quot; aria-label=&quot;what could it look like permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What could it look like&lt;/h2&gt;
&lt;p&gt;Static site generation with Leptos currently consists of two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Collect all static routes&lt;/li&gt;
&lt;li&gt;Build route content&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Leptos compiles some code only for the native target. This code usually serves the website and offers server functions. Part of this code will be filling the data layer from the file system, external APIs, or anywhere else. Once all data is in the ECS, the two steps from above are executed and given access to the data layer. What routes to build can be dependent on the data in the ECS. The same goes for the content of Leptos components in step 2.&lt;/p&gt;
&lt;p&gt;Functionality like converting markdown to HTML could be shared in the form of Bevy ECS systems. Common functionality can be part of the generator itself. More specific systems that, for example, build RSS feeds could be offered by external crates. As long as the generator establishes a set of Bevy components for things like HTML, crates should be compatible with each other and the basic generator should be easy to extend. The Bevy ecosystem shows what is possible in regard to extending an ECS by sharing a set of Components and Systems.&lt;/p&gt;
&lt;h2 id=&quot;what-does-it-look-like&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-does-it-look-like&quot; aria-label=&quot;what does it look like permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What does it look like&lt;/h2&gt;
&lt;p&gt;When I started to write this post, the whole thing was only an idea. Some days later, I had a working integration of Bevy ECS into Leptos and had to give it a name. It&apos;s now called Cinnog&lt;sup id=&quot;fnref-3&quot;&gt;&lt;a href=&quot;#fn-3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. You can find the &lt;a href=&quot;https://github.com/NiklasEi/cinnog&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;main repository&lt;/a&gt; on GitHub as well as an &lt;a href=&quot;https://github.com/NiklasEi/cinnog_example&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;example website&lt;/a&gt; that is hosted on Netlify.&lt;/p&gt;
&lt;p&gt;In the main function of the example website, the data layer is filled from the file system. Some ron files are read from the &lt;code class=&quot;language-text&quot;&gt;people&lt;/code&gt; directory and markdown files are read from the &lt;code class=&quot;language-text&quot;&gt;blog&lt;/code&gt; directory. All markdown in the data layer is then converted to HTML.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[tokio::main]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;io&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataLayer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert_resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SiteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bevy ECS + Leptos = 💕&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read_ron_files_from_directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PersonData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;people&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read_markdown_from_directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PostFrontMatter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;blog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;convert_markdown_to_html&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It&apos;s rather crude, but quite compact and all the used systems are part of Cinnog.&lt;/p&gt;
&lt;p&gt;Data types like &lt;code class=&quot;language-text&quot;&gt;PersonData&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;PostFrontMatter&lt;/code&gt; implement the Trait &lt;code class=&quot;language-text&quot;&gt;Ingest&lt;/code&gt;. This Trait controls which components will be added to the data layer. For example, &lt;code class=&quot;language-text&quot;&gt;PersonData&lt;/code&gt; is split into two components:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(serde::Deserialize)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;PersonData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    name&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    age&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ingest&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PersonData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;ingest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EntityCommands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PersonName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;ingest&lt;/code&gt; method has to be implemented. If the file path is important, &lt;code class=&quot;language-text&quot;&gt;ingest_path&lt;/code&gt; can be overwritten. I started sorting the example blog posts into year directories:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(serde::Deserialize, Default)]&lt;/span&gt;
&lt;span class=&quot;token attribute attr-name&quot;&gt;#[serde(default)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;PostFrontMatter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; test&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; draft&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Ingest&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostFrontMatter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;ingest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EntityCommands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TestFontMatter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;draft &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DraftPost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;ingest_path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EntityCommands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; reg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Regex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;r&quot;/blog/(&amp;lt;year&gt;[0-9]+)/\.*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;caps&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;captures&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_string_lossy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; year &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;caps&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;year&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BlogYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;year&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default_bundle_from_path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So far, I like the direction Cinnog is taking. There are a lot of rough edges, but basic functionality works. Since the Holidays are over, development might slow down a bit. But I will try to get Cinnog to the point where it can be used for this website. It&apos;s definitely a fun project! I might attempt to upstream the required changes to Leptos&lt;sup id=&quot;fnref-4&quot;&gt;&lt;a href=&quot;#fn-4&quot; class=&quot;footnote-ref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;. Smaller things like removing the &lt;code class=&quot;language-text&quot;&gt;.static&lt;/code&gt; file ending for statically generated files &lt;a href=&quot;https://github.com/leptos-rs/leptos/issues/1594#issuecomment-1845939151&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;will probably not be an issue&lt;/a&gt;, but I had to make some changes to &lt;code class=&quot;language-text&quot;&gt;leptos_router&lt;/code&gt; to integrate with Bevy ECS and for those I am unsure how that could be upstreamed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: Cinnog no longer relies on a custom fork of Leptos as of Leptos version 0.6. Cinnog is now available on &lt;a href=&quot;https://crates.io/crates/cinnog&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;crates.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All changes from the custom fork were either upstreamed (&lt;a href=&quot;https://github.com/leptos-rs/leptos/pull/2113&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;#2113&lt;/a&gt; and &lt;a href=&quot;https://github.com/leptos-rs/leptos/pull/2207&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;#2207&lt;/a&gt;) or no longer needed after a refactor of the Bevy ECS integration into Leptos.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;This increases complexity and the gained flexibility might not always be needed. But the generator could use the data layer to easily support a classic structure with data files. Simple projects would almost work out of the box, but if they grow and need more flexibility, the data layer could offer it.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;Properly explaining ECS is out of scope here, but there are already a lot of good resources for that. You could take a look at &lt;a href=&quot;https://github.com/SanderMertens/ecs-faq&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Sander&apos;s ECS FAQ&lt;/a&gt; for general information and the &lt;a href=&quot;https://docs.rs/bevy_ecs/latest/bevy_ecs/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy ECS docs&lt;/a&gt; for some code examples.&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;Following the naming of Leptos (&quot;thin&quot;, &quot;small&quot; in Greek), Cinnog means &quot;small&quot; in one of Tolkien&apos;s Elvish languages. I just thought it reads nice ^^&lt;a href=&quot;#fnref-3&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-4&quot;&gt;Some smaller changes have been upstreamed already: &lt;a href=&quot;https://github.com/leptos-rs/leptos/pull/2113&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;leptos-rs/leptos#2113&lt;/a&gt;&lt;a href=&quot;#fnref-4&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Notes on mobile development with Bevy #2]]></title><description><![CDATA[My Bevy mobile app is very incomplete, but I would like to be able to distribute test builds early on. That means Android builds need to be…]]></description><link>https://www.nikl.me/blog/2023/notes_on_mobile_development_with_bevy_2/</link><guid isPermaLink="false">https://www.nikl.me/blog/2023/notes_on_mobile_development_with_bevy_2/</guid><pubDate>Thu, 03 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My &lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt; mobile app is very incomplete, but I would like to be able to distribute test builds early on. That means Android builds need to be acceptable for the Play Store and iOS builds need to pass Apple&apos;s bar for the App Store. On the way to that goal, there are a couple technical issues to solve and some configuration to do in App Store Connect and Google Play Console. I will concentrate on the technical challenges while mentioning some store configurations.&lt;/p&gt;
&lt;p&gt;Most of this effort did not directly go into my current app project. I decided to add iOS and Android support to &lt;a href=&quot;https://github.com/NiklasEi/bevy_game_template&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_game_template&lt;/code&gt;&lt;/a&gt; first. This way, the community might benefit more from my work and I can just copy the setup for other projects.&lt;/p&gt;
&lt;p&gt;The starting point of this post is the &lt;a href=&quot;https://github.com/bevyengine/bevy/tree/main/examples/mobile&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;mobile example&lt;/a&gt; from Bevy with small adjustments discussed in a &lt;a href=&quot;https://www.nikl.me/blog/2023/notes_on_android_development_using_bevy/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;android&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#android&quot; aria-label=&quot;android permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Android&lt;/h2&gt;
&lt;h3 id=&quot;getting-an-android-app-bundle&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#getting-an-android-app-bundle&quot; aria-label=&quot;getting an android app bundle permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Getting an Android App Bundle&lt;/h3&gt;
&lt;p&gt;The cargo-apk setup from Bevy&apos;s mobile example allows us to build APK files. The Play Store no longer accepts APKs, but &lt;a href=&quot;https://android-developers.googleblog.com/2021/06/the-future-of-android-app-bundles-is.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;requires app bundles&lt;/a&gt;. This makes sense to keep downloads small and still support multiple device ABIs (&lt;a href=&quot;https://en.wikipedia.org/wiki/Application_binary_interface&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;strong&gt;A&lt;/strong&gt;pplication &lt;strong&gt;B&lt;/strong&gt;inary &lt;strong&gt;I&lt;/strong&gt;nterface&lt;/a&gt;), but it requires some extra work to get an app bundle for a Bevy game.&lt;/p&gt;
&lt;p&gt;The only tool I could convince to create an app bundle from my Bevy project is &lt;a href=&quot;https://github.com/rust-mobile/xbuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;xbuild&lt;/a&gt;, the WIP successor of cargo-apk&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. We can configure xbuild to bundle our project for Android with the following &lt;code class=&quot;language-text&quot;&gt;manifest.yaml&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;android&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;gradle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;your_icon.png&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.example.bevygame&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;version_code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bevy game&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Currently, xbuild decides to create app bundles only when using &lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;gradle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt; and only for production builds. Running &lt;code class=&quot;language-text&quot;&gt;x build --platform android --store play&lt;/code&gt; creates an app bundle, but derived APKs instantly crash.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I had to do a couple changes to get xbuild to create working app bundles. Some of them might get upstreamed, but a few are obvious hacks that only work for the Bevy project structure (like a single, hardcoded &lt;code class=&quot;language-text&quot;&gt;assets&lt;/code&gt; directory). For the xcode commands in this post to work as expected, you need to install xbuild from my fork: &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--git&lt;/span&gt; https://github.com/NiklasEi/xbuild&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;libc-shared-is-missing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#libc-shared-is-missing&quot; aria-label=&quot;libc shared is missing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;libc++ shared is missing&lt;/h3&gt;
&lt;p&gt;Looking at the logs with &lt;code class=&quot;language-text&quot;&gt;adb logcat&lt;/code&gt; while trying to start the app on my phone yields the following error:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;log&quot;&gt;&lt;pre class=&quot;language-log&quot;&gt;&lt;code class=&quot;language-log&quot;&gt;&lt;span class=&quot;token property&quot;&gt;java.lang.UnsatisfiedLinkError:&lt;/span&gt; Unable to load native library &lt;span class=&quot;token string&quot;&gt;&quot;/data/app/~~0cxWIprct-rKy7Vggu9E4g==/me.nikl.bevygame-YTVT_qxOwHzm_kGsGV9cYw==/lib/arm64/libmobile.so&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; dlopen failed&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; library &lt;span class=&quot;token string&quot;&gt;&quot;libc++_shared.so&quot;&lt;/span&gt; not found&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The helpful line among hundreds of unhelpful ones.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Opening the APK as an archive indeed shows that &lt;code class=&quot;language-text&quot;&gt;lib/arm64-v8a/&lt;/code&gt; does not include &lt;code class=&quot;language-text&quot;&gt;libc++_shared.so&lt;/code&gt;, while an APK built with cargo-apk includes it. This library is required for audio support in Bevy. I probably could have just ripped out audio for now and moved on, but who am I kidding? I want audio!&lt;/p&gt;
&lt;p&gt;Telling gradle to include &lt;code class=&quot;language-text&quot;&gt;libc++_shared.so&lt;/code&gt; turned out to be more complicated than I had thought. Usually, it should realize on its own that the library is needed and include it. The problem is, that we are not using gradle to build the game library, but cargo. So gradle has no way of knowing that we need &lt;code class=&quot;language-text&quot;&gt;libc++_shared.so&lt;/code&gt;. In the end, I gave up on a &quot;clean&quot; solution and went with a hack. I basically &lt;a href=&quot;https://github.com/NiklasEi/xbuild/commit/a32cdc4300023c81586748b6d8cc9bef6c5e8155&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;told gradle that the project includes a c++ library&lt;/a&gt; which requires the shared library. The c++ project is empty, but it does the trick and creates minimal bloat (~4kB).&lt;/p&gt;
&lt;p&gt;With this change, the &lt;code class=&quot;language-text&quot;&gt;UnsatisfiedLinkError&lt;/code&gt; from above is gone, but the game still doesn&apos;t work.&lt;/p&gt;
&lt;h3 id=&quot;include-assets&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#include-assets&quot; aria-label=&quot;include assets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Include assets&lt;/h3&gt;
&lt;p&gt;While cargo-apk allows simply configuring an asset directory, xbuild does not jet support that. There is an &lt;a href=&quot;https://github.com/rust-mobile/xbuild/pull/122&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;open PR to add support for assets&lt;/a&gt; to the non-gradle builds, but that only works for APKs&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Since Bevy usually comes with a single &lt;code class=&quot;language-text&quot;&gt;assets&lt;/code&gt; directory in the root of the project, &lt;a href=&quot;https://github.com/NiklasEi/xbuild/commit/a32cdc4300023c81586748b6d8cc9bef6c5e8155#diff-6279764828c50df7615545319f05789a4a73bd72f620f9c6033e7d8d712df8c0R101&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;I told gradle to just include that&lt;/a&gt; with a hardcoded path from deep inside the target directory. At this point, the build generated by xbuild using gradle runs properly.&lt;/p&gt;
&lt;h3 id=&quot;continuous-deployment&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#continuous-deployment&quot; aria-label=&quot;continuous deployment permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Continuous deployment&lt;/h3&gt;
&lt;p&gt;The app bundle needs to be signed with jarsigner and can then be uploaded to Google Play Console. Doing this manually for every version is no fun, so I wrote a &lt;a href=&quot;https://www.nikl.me/blog/2023/github_workflow_to_publish_android_app/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitHub workflow for it&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;support-more-android-devices&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#support-more-android-devices&quot; aria-label=&quot;support more android devices permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Support more Android devices&lt;/h3&gt;
&lt;p&gt;After uploading an app bundle, we can use the app bundle explorer in the Google Play Console to check bundle exports for different devices. My early bundles contained libraries for 4 different ABIs due to the dummy c++ library and &lt;a href=&quot;https://developer.android.com/ndk/guides/abis?hl=en#gradle&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;gradle compiling for all non-deprecated ABIs by default&lt;/a&gt;. The game, on the other hand, was only compiled for &lt;code class=&quot;language-text&quot;&gt;arm64-v8a&lt;/code&gt;, which was hardcoded in xbuild.&lt;/p&gt;
&lt;p&gt;The device list in the app bundle explorer can be filtered by ABI. Out of the 18.919 devices, 8.599 are &lt;code class=&quot;language-text&quot;&gt;arm64-v8a&lt;/code&gt; and 10.096 &lt;code class=&quot;language-text&quot;&gt;armeabi-v7a&lt;/code&gt;. Most of the other couple hundred devices are &lt;code class=&quot;language-text&quot;&gt;x86&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;x86_64&lt;/code&gt;&lt;sup id=&quot;fnref-3&quot;&gt;&lt;a href=&quot;#fn-3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. I went back to xbuild, &lt;a href=&quot;https://github.com/NiklasEi/xbuild/commit/57dacffc8e146e2d3cf7ada24eb90f3865fae568&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;fixed the support for &lt;code class=&quot;language-text&quot;&gt;arm&lt;/code&gt; in gradle builds and told it to always build for &lt;code class=&quot;language-text&quot;&gt;arm64&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;arm&lt;/code&gt; when making a build with the &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token parameter variable&quot;&gt;--store&lt;/span&gt;&lt;/code&gt; option&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The bundle still contains lib directories for &lt;code class=&quot;language-text&quot;&gt;x86&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;x86_64&lt;/code&gt; due to the &quot;dummy cpp lib&quot; workaround. Those lib directories tell the play store that our app bundle can generate APKs for those ABIs which is not correct since we do not build our game for them. A &lt;a href=&quot;https://github.com/NiklasEi/xbuild/commit/deddb185d5a8eaf120fff0a144dd4c18156aeecf&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;small gradle configuration&lt;/a&gt; tells it to not build the dummy library for the &lt;code class=&quot;language-text&quot;&gt;x86(64)&lt;/code&gt; ABIs.&lt;/p&gt;
&lt;h2 id=&quot;ios&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#ios&quot; aria-label=&quot;ios permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;iOS&lt;/h2&gt;
&lt;p&gt;I don&apos;t own a Mac or iPhone, but still want to support iOS. So I borrowed a Mac for a couple of days to get everything set up in &lt;code class=&quot;language-text&quot;&gt;bevy_game_template&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first time I tried to let Xcode publish the app with the copied setup from the Bevy mobile example, the process ended in a list of errors from the App Store. The required changes boiled down to adding an app icon and a launch screen.&lt;/p&gt;
&lt;h3 id=&quot;add-icons&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#add-icons&quot; aria-label=&quot;add icons permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Add Icons&lt;/h3&gt;
&lt;p&gt;The App Store requires apps for iOS 11 or later to include app icons with an asset catalog. I did this following the &lt;a href=&quot;https://developer.apple.com/documentation/xcode/configuring-your-app-icon&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;developer documentation from Apple&lt;/a&gt; (&lt;a href=&quot;https://github.com/NiklasEi/bevy_game_template/pull/61/commits/cf89f657ceeed5b244ac6db482f584be8310a27c&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;icon&lt;/a&gt; + &lt;a href=&quot;https://github.com/NiklasEi/bevy_game_template/pull/61/commits/e70601eab140c3d18973d8138999dceed781eedb&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;name&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;adding-a-launch-screen&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#adding-a-launch-screen&quot; aria-label=&quot;adding a launch screen permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding a launch screen&lt;/h3&gt;
&lt;p&gt;Our app requires a launch screen. The &lt;code class=&quot;language-text&quot;&gt;Info.plist&lt;/code&gt; from the Bevy mobile example includes an entry for &lt;code class=&quot;language-text&quot;&gt;UILaunchStoryboardName&lt;/code&gt;, but the launch screen itself is not included. I created one in Xcode following a &lt;a href=&quot;https://stackoverflow.com/a/57201089&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;stack overflow answer&lt;/a&gt; that just contains a centered icon.&lt;/p&gt;
&lt;h3 id=&quot;continuous-deployment-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#continuous-deployment-1&quot; aria-label=&quot;continuous deployment 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Continuous deployment&lt;/h3&gt;
&lt;p&gt;At this point, Xcode can create app builds and push them to App Store Connect. I spent some time on creating a &lt;a href=&quot;https://www.nikl.me/blog/2023/github_workflow_to_publish_ios_app/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;GitHub workflow&lt;/a&gt; to automate the process. The iOS workflow requires a bit more setup than the Android one, but hopefully, I don&apos;t need to organize a Mac again anytime soon.&lt;/p&gt;
&lt;h3 id=&quot;one-issue-left&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#one-issue-left&quot; aria-label=&quot;one issue left permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;One issue left...&lt;/h3&gt;
&lt;p&gt;...the app has no audio on iOS. I have only tested this with &lt;code class=&quot;language-text&quot;&gt;bevy_kira_audio&lt;/code&gt; so far, so &lt;code class=&quot;language-text&quot;&gt;bevy_audio&lt;/code&gt; might be fine. Maybe someone else knows what&apos;s going on here?&lt;/p&gt;
&lt;h2 id=&quot;getting-ready-for-a-release&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#getting-ready-for-a-release&quot; aria-label=&quot;getting ready for a release permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Getting ready for a release&lt;/h2&gt;
&lt;p&gt;Both stores require some setup for the store pages. The app from &lt;code class=&quot;language-text&quot;&gt;bevy_game_template&lt;/code&gt; is not supposed to go live in the app stores, but it would be nice to be able to share a link that allows anyone to install it. For iOS the goal is &quot;external testing&quot; in AppFlight. In Google Play this release type is called &quot;open testing&quot;. Both of these programs require the app to go through a review and the store page to be mostly complete.&lt;/p&gt;
&lt;h3 id=&quot;getting-screenshots&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#getting-screenshots&quot; aria-label=&quot;getting screenshots permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Getting screenshots&lt;/h3&gt;
&lt;p&gt;Screenshots are a big part of finishing the required setup for publishing. Luckily, the last Bevy update came with a very handy screenshot API. Setting the window dimension and adding the below system gives nice screenshots in configurable resolution.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;screenshot_on_spacebar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    input&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Res&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeyCode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    main_window&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;With&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrimaryWindow&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; screenshot_manager&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResMut&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScreenshotManager&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; counter&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Local&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;just_pressed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;KeyCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Space&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./screenshot-{}.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;counter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;counter &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        screenshot_manager
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save_screenshot_to_disk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;main_window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;The system is borrowed from the &lt;a href=&quot;https://github.com/bevyengine/bevy/blob/main/examples/window/screenshot.rs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy screenshot example&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;&lt;a href=&quot;https://github.com/dodorare/crossbow&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Crossbow&lt;/a&gt; might also work, but I didn&apos;t manage to set it up correctly. If you do, please tell me.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;It also doesn&apos;t support globs yet, which is a bit unpractical for Bevy&apos;s asset directory. Every file and subdirectory would need to be listed separately. I added &lt;a href=&quot;https://github.com/rust-mobile/xbuild/commit/ba8d23a955723fe758ee0d1db49c872292c32e57&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;simple glob support for APK builds&lt;/a&gt; on my fork and will try to upstream that once basic assets support has landed.&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;Now I understand why the Bevy mobile example has the &lt;a href=&quot;https://github.com/bevyengine/bevy/blob/v0.11.0/examples/mobile/Cargo.toml#L23&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;two &lt;code class=&quot;language-text&quot;&gt;arm&lt;/code&gt; targets configured&lt;/a&gt; for cargo-apk builds :D&lt;a href=&quot;#fnref-3&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[GitHub workflow to publish a Bevy Android app]]></title><description><![CDATA[This is a guide on how to configure a GitHub workflow for building, signing and publishing an Android app that uses Bevy (there is a…]]></description><link>https://www.nikl.me/blog/2023/github_workflow_to_publish_android_app/</link><guid isPermaLink="false">https://www.nikl.me/blog/2023/github_workflow_to_publish_android_app/</guid><pubDate>Wed, 02 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a guide on how to configure a GitHub workflow for building, signing and publishing an Android app that uses &lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt; (there is a separate &lt;a href=&quot;https://www.nikl.me/blog/2023/github_workflow_to_publish_ios_app/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;guide for iOS&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The workflow uses the tool &lt;a href=&quot;https://github.com/rust-mobile/xbuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;xbuild&lt;/a&gt; from a &lt;a href=&quot;https://github.com/NiklasEi/xbuild&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;customized fork&lt;/a&gt;, which includes a couple fixes and hacks needed to get app bundles. Some of these changes are explained and motivated in &lt;a href=&quot;https://www.nikl.me/blog/2023/notes_on_mobile_development_with_bevy_2/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;a separate post&lt;/a&gt;. If you want to see the workflow in use, you can take a look at &lt;a href=&quot;https://github.com/NiklasEi/bevy_game_template/blob/main/.github/workflows/release-android-google-play.yaml&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;bevy_game_template&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;xbuild-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#xbuild-setup&quot; aria-label=&quot;xbuild setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;xbuild setup&lt;/h2&gt;
&lt;p&gt;You can install xbuild from my fork with &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--git&lt;/span&gt; https://github.com/NiklasEi/xbuild&lt;/code&gt;. Check your environment with &lt;code class=&quot;language-bash&quot;&gt;x doctor&lt;/code&gt; and install missing dependencies.&lt;/p&gt;
&lt;p&gt;To run your project with xbuild, add the following as &lt;code class=&quot;language-text&quot;&gt;manifest.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;android&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;gradle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;icon.png&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.example.app&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;version_code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bevy game&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Make sure to correct the path to your app icon. Also, update the package identifier and the app label.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Connect your Android device to your machine and note down the device id from &lt;code class=&quot;language-bash&quot;&gt;x devices&lt;/code&gt;. Now run &lt;code class=&quot;language-bash&quot;&gt;x run &lt;span class=&quot;token parameter variable&quot;&gt;--device&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;device ID&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;. Gradle should build the project and then start your app on the device. If this works, you can also try building a bundle with &lt;code class=&quot;language-bash&quot;&gt;x build &lt;span class=&quot;token parameter variable&quot;&gt;--release&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--platform&lt;/span&gt; android &lt;span class=&quot;token parameter variable&quot;&gt;--store&lt;/span&gt; play&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-workflow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-workflow&quot; aria-label=&quot;the workflow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The workflow&lt;/h2&gt;
&lt;p&gt;The following goes into a &lt;code class=&quot;language-text&quot;&gt;yaml&lt;/code&gt; file in your GitHub workflows directory (for example &lt;code class=&quot;language-text&quot;&gt;.github/workflows/release-android-google-play.yaml&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; release&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;android&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;play

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GitHub Release&apos;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; string
      &lt;span class=&quot;token key atrule&quot;&gt;play_release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Release name from google play console&apos;&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; string

&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# used for uploading the app to a GitHub release&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bevy_game
  &lt;span class=&quot;token key atrule&quot;&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/x/release/android/mobile.aab&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;PACKAGE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.example.app&quot;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# release track; you can promote a build to &quot;higher&quot; tracks in the play console or publish to a different track directly&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# see track at https://github.com/r0adkll/upload-google-play#inputs for more options&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;TRACK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; internal
  &lt;span class=&quot;token key atrule&quot;&gt;MOBILE_DIRECTORY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mobile

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;bundle-sign-release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install Dependencies
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sudo apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get update; sudo apt&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;get install pkg&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config libx11&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev libasound2&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev libudev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev lld llvm
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v3
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dtolnay/rust&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;toolchain@stable
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Add Android targets
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rustup target add aarch64&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;linux&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;android armv7&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;linux&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;androideabi
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install cargo&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;binstall
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; curl &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;L &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;proto &apos;=https&apos; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tlsv1.2 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;sSf https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//raw.githubusercontent.com/cargo&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;bins/cargo&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;binstall/main/install&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;from&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;binstall&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;release.sh &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; bash
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install xbuild
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cargo binstall &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;git https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//github.com/NiklasEi/xbuild &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;bin&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dir x xbuild &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build app bundle
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          cd ${{ env.MOBILE_DIRECTORY }}
          x doctor
          x build --release --platform android --store play&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; sign app bundle
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          KEYSTORE_PATH=${{ runner.temp }}/upload-keystore.jks
          echo -n &quot;${{ secrets.PLAYSTORE_KEYSTORE }}&quot; | base64 --decode &gt; $KEYSTORE_PATH
          jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore $KEYSTORE_PATH -storepass &quot;${{ secrets.PLAYSTORE_KEYSTORE_PASSWORD }}&quot; ${{ env.BUNDLE_PATH }} upload&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Upload self&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;signed bundle to GitHub
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; svenstaro/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;release&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action@v2
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;repo_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.BUNDLE_PATH &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;asset_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.APP_NAME &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;_$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;_android.aab
          &lt;span class=&quot;token key atrule&quot;&gt;release_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;overwrite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; prepare Google play store secrets
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          SERVICE_ACCOUNT=${{ runner.temp }}/service-account.json
          echo -n &quot;${{ secrets.PLAYSTORE_SERVICE_ACCOUNT }}&quot; | base64 --decode &gt; $SERVICE_ACCOUNT&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; upload bundle to Google play store
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; r0adkll/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;google&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;play@v1
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;serviceAccountJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; runner.temp &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/service&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;account.json
          &lt;span class=&quot;token key atrule&quot;&gt;packageName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.PACKAGE_NAME &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;releaseName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.play_release &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;releaseFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.BUNDLE_PATH &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.TRACK &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Change the &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt; section according to your project. The bundle name is going to be your crate name with an &lt;code class=&quot;language-text&quot;&gt;aab&lt;/code&gt; file ending. If the crate that is built as a library for Android is at the root of your project, remove the &lt;code class=&quot;language-text&quot;&gt;MOBILE_DIRECTORY&lt;/code&gt; variable and its usage. Otherwise, adapt the value.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The workflow requires multiple secrets to be configured in GitHub. You need to have a Google Play Developer account which comes with a one-time 25$ registration fee.&lt;/p&gt;
&lt;p&gt;Before configuring the required secrets, let&apos;s quickly go through the workflow steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install dependencies for Bevy and xbuild&lt;/li&gt;
&lt;li&gt;Check out the repository&lt;/li&gt;
&lt;li&gt;Install the stable toolchain of Rust&lt;/li&gt;
&lt;li&gt;Install Rust targets for Android&lt;/li&gt;
&lt;li&gt;Install cargo-binstall to significantly speed up the next step&lt;/li&gt;
&lt;li&gt;Install xbuild from my fork using a prebuilt binary&lt;/li&gt;
&lt;li&gt;Create an app bundle with xbuild&lt;/li&gt;
&lt;li&gt;Sign the app bundle using jarsigner&lt;/li&gt;
&lt;li&gt;Upload the bundle to a GitHub release&lt;/li&gt;
&lt;li&gt;Prepare the Google Play Store service account secret&lt;/li&gt;
&lt;li&gt;Upload the bundle to Google Play Store&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Produced app bundles will contain libraries for the ABIs &lt;code class=&quot;language-text&quot;&gt;arm64-v8a&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;armeabi-v7a&lt;/code&gt;, which cover more than 90% of the devices currently supported by Android (see &lt;a href=&quot;https://www.nikl.me/blog/2023/notes_on_mobile_development_with_bevy_2#support-more-android-devices&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Notes on mobile development with Bevy #2&lt;/a&gt; for more details).&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-secrets&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#setting-up-the-secrets&quot; aria-label=&quot;setting up the secrets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting up the secrets&lt;/h2&gt;
&lt;p&gt;Simple strings like passwords can directly go into a GitHub secret. Files will be encoded first using base64 (e.g. &lt;code class=&quot;language-bash&quot;&gt;openssl base64 &lt;span class=&quot;token parameter variable&quot;&gt;-in&lt;/span&gt; ~/upload-keystore.jks&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To configure a secret go to your repository settings in GitHub. Navigate to &quot;Security&quot; -&gt; &quot;Secrets and variables&quot;, select &quot;Actions&quot; then click &quot;New repository secret&quot;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PLAYSTORE_KEYSTORE&lt;/strong&gt; and &lt;strong&gt;PLAYSTORE_KEYSTORE_PASSWORD&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;You can generate a keystore with &lt;code class=&quot;language-bash&quot;&gt;keytool &lt;span class=&quot;token parameter variable&quot;&gt;-genkey&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-keystore&lt;/span&gt; ~/upload-keystore.jks &lt;span class=&quot;token parameter variable&quot;&gt;-keyalg&lt;/span&gt; RSA &lt;span class=&quot;token parameter variable&quot;&gt;-keysize&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-validity&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-alias&lt;/span&gt; upload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;add the password and the base64 encoded keystore to GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PLAYSTORE_SERVICE_ACCOUNT&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;go to the &lt;a href=&quot;https://console.developers.google.com/iam-admin/serviceaccounts&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Service accounts page&lt;/a&gt; to create a new service account
&lt;ul&gt;
&lt;li&gt;select a project or create a new one, then click on &quot;Create service account&quot;&lt;/li&gt;
&lt;li&gt;set a name and press &quot;Continue&quot;&lt;/li&gt;
&lt;li&gt;click &quot;Select a role&quot;, then find and select &quot;Service Accounts&quot; -&gt; &quot;Service Account User&quot;, and press &quot;Done&quot;&lt;/li&gt;
&lt;li&gt;open the &quot;Actions&quot; vertical three-dot menu of the service account you just created&lt;/li&gt;
&lt;li&gt;select &quot;Manage keys&quot; and click &quot;Add Key&quot; + &quot;Create New Key&quot;&lt;/li&gt;
&lt;li&gt;chose the &lt;code class=&quot;language-text&quot;&gt;json&lt;/code&gt; type and press &quot;Create&quot;&lt;/li&gt;
&lt;li&gt;the key should be automatically downloaded to your machine&lt;/li&gt;
&lt;li&gt;encode the key and add it to GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;visit the Google Play Console and navigate to &quot;Setup&quot; -&gt; &quot;API access&quot;&lt;/li&gt;
&lt;li&gt;under &quot;Service accounts&quot;, find your newly created account and click &quot;Manage Play Console permissions&quot;&lt;/li&gt;
&lt;li&gt;go to the &quot;App permissions&quot; tab and add your app&lt;/li&gt;
&lt;li&gt;click &quot;Invite user&quot; to finish the process&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before running the workflow for the first time, got to Play Store Connect and create a release in &quot;Internal testing&quot;. Remember the name (e.g. &quot;v0.1.0&quot;) since you will have to pass it to the workflow. Create a bundle on your machine following the workflow steps 7 and 8, then upload it manually to the release. Otherwise, the Google Play API will return the error &quot;Package not found&quot;.&lt;/p&gt;
&lt;p&gt;Now you can head over to the &quot;Actions&quot; tab in your repository. Find the workflow in the list on the left, select it and click &quot;Run workflow&quot; in the top right. Put a name for a GitHub release in the first input and the release name from Google Play Console in the second and press &quot;Run workflow&quot;.&lt;/p&gt;
&lt;h2 id=&quot;final-comments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#final-comments&quot; aria-label=&quot;final comments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Final comments&lt;/h2&gt;
&lt;p&gt;The app build number is taken from the &lt;code class=&quot;language-text&quot;&gt;manifest.yaml&lt;/code&gt; and needs to be bumped for every build. Otherwise, the upload is not accepted by Google Play Console.&lt;/p&gt;
&lt;p&gt;Workflow runs are free for public repositories on GitHub. If your project is private, it will use build minutes from your allowance (&lt;a href=&quot;https://github.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;2000 minutes per month on a free account&lt;/a&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[GitHub workflow to publish an iOS app]]></title><description><![CDATA[Last update Jan 24 2026 (fixed an appstore connect link) I built a GitHub workflow to bundle, sign and publish an iOS app. There are already…]]></description><link>https://www.nikl.me/blog/2023/github_workflow_to_publish_ios_app/</link><guid isPermaLink="false">https://www.nikl.me/blog/2023/github_workflow_to_publish_ios_app/</guid><pubDate>Sat, 29 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Last update Jan 24 2026 (fixed an appstore connect link)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I built a GitHub workflow to bundle, sign and publish an iOS app. There are already a couple guides on how to do this, but none of them worked out of the box for me (an iOS/mac noob). With lots of googling plus trial and error, I pieced together the missing and broken parts. Since I wouldn&apos;t wish that on anyone, here is some information on how to set up the workflow.&lt;/p&gt;
&lt;p&gt;The project that the workflow was build for might be a bit unconventional. It&apos;s a mobile app using the Rust game engine &lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt;. The workflow expects a specific project structure with the Xcode project living in a subdirectory. You can change the name of the subdirectory and the Xcode project in the &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt; section. The whole workflow is part of this post, but if you want to see it in use, you can look at &lt;a href=&quot;https://github.com/NiklasEi/bevy_game_template/blob/main/.github/workflows/release-ios-testflight.yaml&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;bevy_game_template&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-workflow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-workflow&quot; aria-label=&quot;the workflow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Workflow&lt;/h2&gt;
&lt;p&gt;The following goes into a &lt;code class=&quot;language-text&quot;&gt;yaml&lt;/code&gt; file in your GitHub workflows directory (for example &lt;code class=&quot;language-text&quot;&gt;.github/workflows/release-ios-testflight.yaml&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yaml&quot;&gt;&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; release&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ios&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;testflight

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Version - e.g. v1.2.3&apos;&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; string

&lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;# used for uploading the app to a GitHub release&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;APP_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bevy_game
   &lt;span class=&quot;token key atrule&quot;&gt;XCODE_PROJECT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mobile
   &lt;span class=&quot;token key atrule&quot;&gt;MOBILE_DIRECTORY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mobile

&lt;span class=&quot;token key atrule&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; write

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
   &lt;span class=&quot;token key atrule&quot;&gt;build-for-iOS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; macos&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
      &lt;span class=&quot;token key atrule&quot;&gt;timeout-minutes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v3
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dtolnay/rust&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;toolchain@stable
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Add iOS targets
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rustup target add aarch64&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;apple&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ios
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install the Apple certificate and provisioning profile
           &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; profile
           &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;IOS_CERTIFICATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_CERTIFICATE &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;IOS_CERTIFICATE_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_CERTIFICATE_PASSWORD &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;IOS_PROVISION_PROFILE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_PROVISION_PROFILE &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;IOS_KEYCHAIN_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_KEYCHAIN_PASSWORD &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
              # create variables
              CERTIFICATE_PATH=${{ runner.temp }}/build_certificate.p12
              PP_PATH=${{ runner.temp }}/profile.mobileprovision
              KEYCHAIN_PATH=${{ runner.temp }}/app-signing.keychain-db&lt;/span&gt;

              &lt;span class=&quot;token comment&quot;&gt;# import certificate and provisioning profile from secrets&lt;/span&gt;
              echo &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;n &quot;$IOS_CERTIFICATE&quot; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; base64 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;decode &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;o $CERTIFICATE_PATH
              echo &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;n &quot;$IOS_PROVISION_PROFILE&quot; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; base64 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;decode &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;o $PP_PATH
              uuid=`grep UUID &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;A1 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;a $PP_PATH &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; grep &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;io &quot;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;A&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;F0&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;\&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;36\&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;`
              echo &quot;uuid=$uuid&quot; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; $GITHUB_OUTPUT

              &lt;span class=&quot;token comment&quot;&gt;# create temporary keychain&lt;/span&gt;
              security create&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;keychain &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p &quot;$IOS_KEYCHAIN_PASSWORD&quot; $KEYCHAIN_PATH
              security set&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;keychain&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;settings &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lut 21600 $KEYCHAIN_PATH
              security unlock&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;keychain &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p &quot;$IOS_KEYCHAIN_PASSWORD&quot; $KEYCHAIN_PATH

              &lt;span class=&quot;token comment&quot;&gt;# import certificate to keychain&lt;/span&gt;
              security import $CERTIFICATE_PATH &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;P &quot;$IOS_CERTIFICATE_PASSWORD&quot; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;A &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;t cert &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;f pkcs12 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;k $KEYCHAIN_PATH
              security list&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;keychain &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;d user &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;s $KEYCHAIN_PATH

              &lt;span class=&quot;token comment&quot;&gt;# apply provisioning profile&lt;/span&gt;
              mkdir &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p ~/Library/MobileDevice/Provisioning\ Profiles
              cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$uuid.mobileprovision
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build app for iOS
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
              cd ${{ env.MOBILE_DIRECTORY }}
              xcodebuild PROVISIONING_PROFILE=${{ steps.profile.outputs.uuid }} -scheme ${{ env.XCODE_PROJECT }} clean archive -archivePath &quot;Actions&quot; -configuration Release -arch arm64&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; export ipa
           &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;EXPORT_PLIST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_EXPORT_PRODUCTION &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
              EXPORT_PLIST_PATH=${{ runner.temp }}/ExportOptions.plist
              echo -n &quot;$EXPORT_PLIST&quot; | base64 --decode --output $EXPORT_PLIST_PATH
              xcodebuild PROVISIONING_PROFILE=${{ steps.profile.outputs.uuid }} -exportArchive -archivePath ${{ env.MOBILE_DIRECTORY }}/Actions.xcarchive -exportOptionsPlist $EXPORT_PLIST_PATH -exportPath ${{ runner.temp }}/export&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; decode API key
           &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;API_KEY_BASE64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.IOS_APPSTORE_API_PRIVATE_KEY &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
              mkdir -p ~/private_keys
              echo -n &quot;$API_KEY_BASE64&quot; | base64 --decode --output ~/private_keys/AuthKey_${{ secrets.IOS_APPSTORE_API_KEY_ID }}.p8&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Upload to testflight
           &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
              xcrun altool --validate-app -f ${{ runner.temp }}/export/${{ env.XCODE_PROJECT }}.ipa -t ios --apiKey ${{ secrets.IOS_APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.IOS_APPSTORE_ISSUER_ID }}
              xcrun altool --upload-app -f ${{ runner.temp }}/export/${{ env.XCODE_PROJECT }}.ipa -t ios --apiKey ${{ secrets.IOS_APPSTORE_API_KEY_ID }} --apiIssuer ${{ secrets.IOS_APPSTORE_ISSUER_ID }}&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Upload release
           &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; svenstaro/upload&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;release&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;action@v2
           &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;repo_token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.GITHUB_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; runner.temp &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/export/$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.XCODE_PROJECT &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;.ipa
              &lt;span class=&quot;token key atrule&quot;&gt;asset_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env.APP_NAME &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;_$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;_ios.ipa
              &lt;span class=&quot;token key atrule&quot;&gt;release_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; inputs.version &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
              &lt;span class=&quot;token key atrule&quot;&gt;overwrite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Configure the &lt;code class=&quot;language-text&quot;&gt;env&lt;/code&gt; section according to your project.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The workflow requires multiple secrets to be configured in GitHub. Some of those need an active membership in the Apple developer program, which costs 99$ per year. We&apos;ll get back to getting and configuring the secrets later. First, let&apos;s go through the workflow steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out the repository&lt;/li&gt;
&lt;li&gt;Install the stable toolchain of Rust (remove this in case you do not use Rust)&lt;/li&gt;
&lt;li&gt;Install Rust targets for iOS (remove this in case you do not use Rust)&lt;/li&gt;
&lt;li&gt;Some of the required secrets need to be written to files in specific locations. The build certificate and provisioning profile are files encoded in base64 and need to be decoded.
&lt;ul&gt;
&lt;li&gt;The build certificate is imported into a temporary keychain using its password. Xcode will read the certificate from the keychain automatically.&lt;/li&gt;
&lt;li&gt;Provisioning profiles are picked up by Xcode from &lt;code class=&quot;language-text&quot;&gt;~/Library/MobileDevice/Provisioning\ Profiles/&lt;/code&gt;. I use its uuid as file name, because I had some issues where Xcode couldn&apos;t find the correct profile, but I am not sure if this is actually needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code class=&quot;language-bash&quot;&gt;xcodebuild&lt;/code&gt; to archive your project.&lt;/li&gt;
&lt;li&gt;Use &lt;code class=&quot;language-text&quot;&gt;ExportOptions.plist&lt;/code&gt; to export an &lt;code class=&quot;language-text&quot;&gt;ipa&lt;/code&gt; from the archive.&lt;/li&gt;
&lt;li&gt;Decode the API to talk to the App Store. The key is moved to &lt;code class=&quot;language-text&quot;&gt;~/private_keys&lt;/code&gt; and needs to contain the key id in its name for &lt;code class=&quot;language-bash&quot;&gt;altool&lt;/code&gt; to find it.&lt;/li&gt;
&lt;li&gt;Talk to the App Store to validate and upload the &lt;code class=&quot;language-text&quot;&gt;ipa&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Upload the self-signed &lt;code class=&quot;language-text&quot;&gt;ipa&lt;/code&gt; to a GitHub release.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before starting the workflow for the first time, your app should be configured in App Store Connect. The builds will automatically show up in TestFlight after a short processing period.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-the-secrets&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#setting-up-the-secrets&quot; aria-label=&quot;setting up the secrets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting up the secrets&lt;/h2&gt;
&lt;p&gt;Simple strings like passwords can directly go into a GitHub secret. Files will be encoded first using base64 (e.g. &lt;code class=&quot;language-bash&quot;&gt;openssl base64 &lt;span class=&quot;token parameter variable&quot;&gt;-in&lt;/span&gt; ~/ExportOptions.plist&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To configure a secret go to your repository settings in GitHub. Navigate to &lt;em&gt;Security&lt;/em&gt; - &lt;em&gt;Secrets and variables&lt;/em&gt;, select &lt;em&gt;Actions&lt;/em&gt; then click &quot;New repository secret&quot;.&lt;/p&gt;
&lt;p&gt;You will need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IOS_CERTIFICATE&lt;/strong&gt; and &lt;strong&gt;IOS_CERTIFICATE_PASSWORD&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;You can use Xcode to &lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev154b28f09?sub=dev23755c6c6&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;create a certificate&lt;/a&gt; for you
&lt;ul&gt;
&lt;li&gt;Use the &lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev80c6204ec&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;certificate type&lt;/a&gt; &lt;code class=&quot;language-text&quot;&gt;Apple Distribution&lt;/code&gt; to be able to publish the builds in the store&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev154b28f09?sub=dev6dab365c2&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Export the certificate&lt;/a&gt; as &lt;code class=&quot;language-text&quot;&gt;.p12&lt;/code&gt; from xbuild and set the password while doing so&lt;/li&gt;
&lt;li&gt;The exported &lt;code class=&quot;language-text&quot;&gt;.p12&lt;/code&gt; file needs to be base64 encoded before adding it as a GitHub secret&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IOS_PROVISION_PROFILE&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;After creating your distribution certificate, you can visit your &lt;a href=&quot;https://developer.apple.com/account/resources/profiles/list&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;developer account resources&lt;/a&gt; to create a new distribution profile. Select &quot;+&quot;, chose &lt;em&gt;Distribution&lt;/em&gt; - &lt;em&gt;App Store&lt;/em&gt;, then select your app and the previously created distribution certificate. The final step is setting the name of your profile, which you should remember for the export options further down. After clicking &quot;Generate&quot;, you can download your &lt;code class=&quot;language-text&quot;&gt;.mobileprovision&lt;/code&gt; file and proceed with base64 encoding and configuring the secret in GitHub.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IOS_KEYCHAIN_PASSWORD&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;This can be any string since it is only used to create and open a temporary keychain on the action runner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IOS_EXPORT_PRODUCTION&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;You can start off your &lt;code class=&quot;language-text&quot;&gt;ExportOptions.plist&lt;/code&gt; with the following:
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;plist&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;PUBLIC&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-//Apple//DTD PLIST 1.0//EN&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;plist&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;destination&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;export&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;provisioningProfiles&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
               &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{application identifier e.g. me.nikl.bevygame}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
               &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{name of distribution profile}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;manageAppVersionAndBuildNumber&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;method&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;app-store&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;signingStyle&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;manual&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;stripSwiftSymbols&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;teamID&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{your team id}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;uploadSymbols&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dict&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;plist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Replace the application identifier, provisioning profile name and team ID
&lt;ul&gt;
&lt;li&gt;You can find your teamID under &quot;Membership details&quot; in your &lt;a href=&quot;https://developer.apple.com/account&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;account info&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Encode the file using base64 and add it to GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IOS_APPSTORE_API_PRIVATE_KEY&lt;/strong&gt; and &lt;strong&gt;IOS_APPSTORE_API_KEY_ID&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Follow &lt;a href=&quot;https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api#3028599&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;the docs to create&lt;/a&gt; a new API key&lt;/li&gt;
&lt;li&gt;You can use &lt;a href=&quot;https://developer.apple.com/support/roles/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;the role&lt;/a&gt; &quot;Developer&quot;&lt;/li&gt;
&lt;li&gt;Download (&lt;code class=&quot;language-text&quot;&gt;.p8&lt;/code&gt;) and encode the key using base64 before adding it as a secret&lt;/li&gt;
&lt;li&gt;Copy the key ID of the just created key from the table&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IOS_APPSTORE_ISSUER_ID&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Visit &quot;&lt;a href=&quot;https://appstoreconnect.apple.com/access/integrations/api&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Users and Access&lt;/a&gt;&quot; to copy the issuer ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that all required secrets are configured, head over to the &quot;Actions&quot; tab in your repository. Find the workflow in the list on the left, select it and click &quot;Run workflow&quot; in the top right.&lt;/p&gt;
&lt;h2 id=&quot;final-comments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#final-comments&quot; aria-label=&quot;final comments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Final comments&lt;/h2&gt;
&lt;p&gt;The version input of the workflow is only used as GitHub release to upload the artifact. The actual app version is defined in your &lt;code class=&quot;language-text&quot;&gt;Info.plist&lt;/code&gt; and needs to be new for the App Store to accept your build.&lt;/p&gt;
&lt;p&gt;You cannot easily install the signed ipa on any device without going through the App Store or TestFlight. For macOS apps, there is a different certificate type called &quot;Developer ID Installer&quot; to do so, but there doesn&apos;t seem to be something similar for iOS apps.&lt;/p&gt;
&lt;p&gt;Workflow runs are free for public repositories on GitHub. If your project is private, it will use build minutes from your allowance (&lt;a href=&quot;https://github.com/pricing&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;2000 minutes per month on a free account&lt;/a&gt;). Know that &lt;a href=&quot;https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#minute-multipliers&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;every minute with a macOS runner is billed as 10 minutes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In case you are also interested in Android development with Bevy, there is a separate &lt;a href=&quot;https://www.nikl.me/blog/2023/github_workflow_to_publish_android_app/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;post about a workflow for Android builds&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Notes on Android development using Bevy]]></title><description><![CDATA[While working on an Android logic puzzle app with Bevy, I learned some things about the current state of Android support in the engine and…]]></description><link>https://www.nikl.me/blog/2023/notes_on_android_development_using_bevy/</link><guid isPermaLink="false">https://www.nikl.me/blog/2023/notes_on_android_development_using_bevy/</guid><pubDate>Sat, 08 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While working on an Android logic puzzle app with &lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt;, I learned some things about the current state of Android support in the engine and its dependencies. The following are a few notes that hopefully are not only useful to myself, but also to others who would like to use Bevy to write an Android app.&lt;/p&gt;
&lt;p&gt;The starting point is &lt;a href=&quot;https://github.com/bevyengine/bevy/tree/main/examples/mobile&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&apos;s mobile example&lt;/a&gt;. Take a look at &lt;a href=&quot;https://github.com/bevyengine/bevy/blob/main/examples/README.md#android&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;the example Readme for instructions on how to run it&lt;/a&gt;. For now, I will ignore all things iOS since I don&apos;t have a setup for iOS development. When my app was only a copy of the Bevy example, I could connect my phone, run &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; apk run &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;app_lib_name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt; and the example would open up after around one and a half minutes. Let&apos;s work on that.&lt;/p&gt;
&lt;h2 id=&quot;shorter-feedback-cycles&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#shorter-feedback-cycles&quot; aria-label=&quot;shorter feedback cycles permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Shorter feedback cycles&lt;/h2&gt;
&lt;p&gt;Installing the app on my phone should be significantly faster than one minute to not constantly wait after code changes. &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; apk run&lt;/code&gt; mostly does three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compile your code as &lt;code class=&quot;language-text&quot;&gt;cdylib&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bundle and sign the APK&lt;/li&gt;
&lt;li&gt;Install the APK on the connected device&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Looking at the first step already, we can half the compile time of the Bevy example. It configures two android targets &lt;code class=&quot;language-text&quot;&gt;aarch64-linux-android&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;armv7-linux-androideabi&lt;/code&gt;. Every code change you make will be compiled for both targets, but my phone ran fine with only either of them configured. For now, I only build for &lt;code class=&quot;language-text&quot;&gt;aarch64-linux-android&lt;/code&gt;. When it comes to releasing the app, I might revisit this and add &lt;a href=&quot;https://doc.rust-lang.org/nightly/rustc/platform-support/android.html#building-the-target&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;more targets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second step is pretty fast already. One point about the signing though: if you would like a release build, &lt;code class=&quot;language-text&quot;&gt;cargo-apk&lt;/code&gt; will complain, that you need a key. If you only want something quick for testing, you can point to the same key that is already used for debug builds:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;toml&quot;&gt;&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;package.metadata.android.signing.release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.android/debug.keystore&quot;&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;keystore_password&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Configuration for signing a release APK with the debug key. This is the path to the debug keystore on my linux laptop, but it should be similar for other OS.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For incremental installs, the third step took by far the longest. More than a minute was spent on copying the APK to my phone. This was easily explained when looking at the size of the bundled APK: 1.5Gb. Half of the APK was gone once I removed a target, but ~750Mb is still too much.&lt;/p&gt;
&lt;p&gt;Looking at the documentation of &lt;code class=&quot;language-text&quot;&gt;cargo-apk&lt;/code&gt;, I found an option to strip the shared libraries from debug symbols. This is done by default for release builds, but you can enable it for all builds by configuring &lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;strip&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;strip&quot;&lt;/span&gt;&lt;/code&gt; under &lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;package.metadata.android&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt; (&lt;a href=&quot;https://github.com/bevyengine/bevy/pull/8932&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;this is now also done for the Bevy example&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;While developing android specific things, I can now run &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-x&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;apk run -p &amp;lt;app_lib_name&gt;&apos;&lt;/span&gt;&lt;/code&gt; and the app opens up in acceptable time (compile + ~6s).&lt;/p&gt;
&lt;h3 id=&quot;desktop-build&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#desktop-build&quot; aria-label=&quot;desktop build permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Desktop build&lt;/h3&gt;
&lt;p&gt;Even with all the size improvements for our debug APK, running the application on the phone takes longer than on desktop. For code changes that are not platform specific, it&apos;s helpful to have a desktop target set up. Especially, because I can&apos;t get the app to run in an emulator.&lt;/p&gt;
&lt;p&gt;Since &lt;code class=&quot;language-text&quot;&gt;cargo-apk&lt;/code&gt; requires a &lt;code class=&quot;language-text&quot;&gt;cdylib&lt;/code&gt;, I split the project into two crates. One crate contains a library exporting everything as a plugin and a &lt;code class=&quot;language-text&quot;&gt;main.rs&lt;/code&gt; file to target desktop. The other crate only contains a &lt;code class=&quot;language-text&quot;&gt;lib.rs&lt;/code&gt; file, uses the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[bevy_main]&lt;/span&gt;&lt;/code&gt; macro, and configures the android application in its &lt;code class=&quot;language-text&quot;&gt;Cargo.toml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now &lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cargo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-x&lt;/span&gt; run&lt;/code&gt; restarts the app on my desktop after every code change.&lt;/p&gt;
&lt;h2 id=&quot;where-to-render&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#where-to-render&quot; aria-label=&quot;where to render permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Where to render&lt;/h2&gt;
&lt;p&gt;Different mobile devices have different screen geometries and cut-outs for e.g. cameras. A mobile application should not render content behind the info bar or software buttons. This is one of the challenges I have not completely solved yet.&lt;/p&gt;
&lt;p&gt;My current attempt is the following system that runs in an early state and every time the orientation changes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;calculate_layout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Commands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    windows&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NonSend&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WinitWindows&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    primary_window&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Entity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;With&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PrimaryWindow&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token macro property&quot;&gt;info!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Calculating layout&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; primary_entity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; primary_window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; primary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; windows&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;primary_entity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unwrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; inner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; primary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inner_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; scale &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; primary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scale_factor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; content_rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token macro property&quot;&gt;cfg!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;target_os &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;android&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;winit&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;platform&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;android&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WindowExtAndroid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; content_rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; primary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;content_rect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContentRect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            bottom&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; content_rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bottom &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content_rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; content_rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            top&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; content_rect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; scale &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token macro property&quot;&gt;info!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Adjusting content to {content:?} due to inner {inner:?}, content {content_rect:?} and scale {scale}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        content
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;ContentRect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            bottom&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            top&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert_resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Layout&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; content_rect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Resource)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;Layout&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;crate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; content_rect&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContentRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;ContentRect&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; top&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; bottom&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This system calculates the distance in pixels that content should keep from the borders of the screen to not overlap with cut-outs or things like software buttons. The pixel values are stored in a resource and default to 0 on other platforms than Android.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The idea is to use the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Layout&lt;/span&gt;&lt;/code&gt; resource in all systems that spawn sprites or UI and use the &lt;code class=&quot;language-text&quot;&gt;top&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;bottom&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;left&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;right&lt;/code&gt; values to keep appropriate distance from software buttons and cut-outs. On my S10+, this works perfectly for the software buttons on the bottom, but not for the info bar on top of my screen. The content rect reports too big numbers for the top and I opened &lt;a href=&quot;https://github.com/rust-windowing/winit/issues/2931&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;rust-windowing/winit#2931&lt;/a&gt; for that (but I am not sure if this is really a winit issue or further upstream).&lt;/p&gt;
&lt;h2 id=&quot;performance&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#performance&quot; aria-label=&quot;performance permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Performance&lt;/h2&gt;
&lt;p&gt;A simple app should not constantly drain battery while running 60 FPS for nothing. The first thing we can do is to tell winit to only run frames when something happened (like user input). In Bevy, we can do this by adding the resource &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;WinitSettings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;desktop_app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt; to our app. This configures winit to run only one frame every 5 seconds while the application is open, but not receiving any user input.&lt;/p&gt;
&lt;p&gt;These winit settings can break animations, since the screen effectively freezes without user input. If you know that you want to run the next frame independent of the user doing something or not, you can send a &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;RequestRedraw&lt;/span&gt;&lt;/code&gt; event.&lt;/p&gt;
&lt;h2 id=&quot;backward-navigation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#backward-navigation&quot; aria-label=&quot;backward navigation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Backward navigation&lt;/h2&gt;
&lt;p&gt;My app will only contain logic puzzles and as such doesn&apos;t need input apart from touch events; I thought. Then I attempted to use the software back button on my phone and didn&apos;t get any keyboard input. This seems to be an open issue in winit (&lt;a href=&quot;https://github.com/rust-windowing/winit/issues/2304&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;rust-windowing/winit#2304&lt;/a&gt;). After a little investigation, &lt;a href=&quot;https://github.com/bevyengine/bevy/commit/bc88b818b0de76aa7249c07524803144b9790949&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;I hacked something in my bevy fork and moved on&lt;/a&gt; (for now).&lt;/p&gt;
&lt;p&gt;On desktop, I am used to navigating backwards with my mouse. This is not supported in the latest winit release, &lt;a href=&quot;https://github.com/rust-windowing/winit/pull/2770&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;but was merged recently&lt;/a&gt; and should make its way to Bevy soon. For now, I hardcoded &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;MouseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Other&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt; to be back navigation.&lt;/p&gt;
&lt;h2 id=&quot;moving-the-app-to-the-background&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#moving-the-app-to-the-background&quot; aria-label=&quot;moving the app to the background permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Moving the app to the background&lt;/h2&gt;
&lt;p&gt;Anyone playing around with Bevy on Android will notice, that the App lifecycle events are not yet completely handled. When you minimize and open the app back up, it will not resume where you left of, but start from the beginning (Update - Fixed in Bevy 0.12). This issue is known and solutions were discussed a bit in &lt;a href=&quot;https://github.com/bevyengine/bevy/issues/86&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;bevyengine/bevy#86&lt;/a&gt;, but since I couldn&apos;t find an open issue for the specific problem, I opened &lt;a href=&quot;https://github.com/bevyengine/bevy/issues/9057&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;bevyengine/bevy#9057&lt;/a&gt; to track it.
For now, my workaround in the app is to save the current state in a database, so the app can always go back to where the user left.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There is &lt;a href=&quot;https://www.nikl.me/blog/2023/notes_on_mobile_development_with_bevy_2/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;a follow-up post&lt;/a&gt; concentrating on how to get a Bevy application into the app stores&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dynamic assets]]></title><description><![CDATA[One of my favorite Bevy projects at the moment is the plugin bevy_asset_loader (repository). Its goal is to minimize boilerplate for asset…]]></description><link>https://www.nikl.me/blog/2022/dynamic-assets-in-bevy_asset_loader/</link><guid isPermaLink="false">https://www.nikl.me/blog/2022/dynamic-assets-in-bevy_asset_loader/</guid><pubDate>Tue, 01 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;One of my favorite Bevy projects at the moment is the plugin &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;bevy_asset_loader&lt;/a&gt; (&lt;a href=&quot;https://github.com/NiklasEi/bevy_asset_loader&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;repository&lt;/a&gt;). Its goal is to minimize boilerplate for asset handling while improving code readability and maintainability for games with a lot of assets. I wrote about the motivation and basic idea of the plugin in &lt;a href=&quot;/blog/2021/asset-handling-in-bevy-apps&quot;&gt;a previous blog post&lt;/a&gt;. Most of the future functionality mentioned in the post is now implemented. The biggest feature added to &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt; since then, was not discussed though. That&apos;s what this post is all about.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-idea&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-idea&quot; aria-label=&quot;the idea permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The idea&lt;/h2&gt;
&lt;p&gt;In the beginning of &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt;, all the information needed to load a certain asset was given at compile time. Things like the file path, type information (load it as a standard material plz), or tile sizes for a sprite sheet are passed in derive macro attributes.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/player.png&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; player&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(texture_atlas(tile_size_x = 64., tile_size_y = 32., columns = 6, rows = 1))]&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/dog_sprite_sheet.png&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; dog&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Giving all asset configuration at compile time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At compile time the derive macro will &quot;hardcode&quot; the file paths and other configuration in the generated code to load the assets. The information directly on the asset collection structs is convenient and reads very well. But there is an issue with this.&lt;/p&gt;
&lt;p&gt;If different people (or even different teams) take care of assets and coding, the information seems to be at the wrong place. Tile sizes for a sprite sheet might change with updates on the sprite sheet. Changing this configuration feels like it should happen outside the code. The configuration for an asset should be an asset itself.&lt;/p&gt;
&lt;h2 id=&quot;dynamic-assets&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dynamic-assets&quot; aria-label=&quot;dynamic assets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dynamic assets&lt;/h2&gt;
&lt;p&gt;In some way, configuration and asset handle still need to be connected. I went for the simplest approach and use string keys to connect them. These keys need to be given in our asset collections as derive macro attributes. From now on I will call assets &quot;dynamic&quot; if they have a string key and no other hardcoded asset configuration like a file path.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(key = &lt;span class=&quot;token string&quot;&gt;&quot;player&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; player&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(key = &lt;span class=&quot;token string&quot;&gt;&quot;dog&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; dog&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;An asset collection with &quot;dynamic assets&quot;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At compile time, the file paths and texture atlas configuration are not known. The code generated by the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetCollection&lt;/span&gt;&lt;/code&gt; derive macro uses the string key to grab all asset configuration at run time. &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt; stores the key &amp;#x3C;-&gt; asset configuration mapping in a resource. Users can either manage it manually and configure all used keys before the loading state, or load everything from ron files.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;setup_system&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; asset_keys&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResMut&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetKeys&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    asset_keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register_asset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;player&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;DynamicAsset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;File&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            path&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;textures/player.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    asset_keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register_asset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;dog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;DynamicAsset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            path&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;textures/dog_sprite_sheet.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;to_owned&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            tile_size_x&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            tile_size_y&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            columns&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            rows&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Manually registering the required asset keys to load the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt;&lt;/code&gt; asset collection.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Manual registration does not solve the original problem. The asset configuration is still a part of our code. This is why I think the main use case of dynamic assets is the possibility to load key &amp;#x3C;-&gt; asset configuration mappings from &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; files. If you enable the feature &lt;code class=&quot;language-rust&quot;&gt;dynamic_assets&lt;/code&gt;, the following file can be loaded by &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt;. All asset keys will be automatically registered before dynamic assets are resolved.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ron&quot;&gt;&lt;pre class=&quot;language-ron&quot;&gt;&lt;code class=&quot;language-ron&quot;&gt;(&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;&quot;player&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; File (
        &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;textures/player.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    )&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;&quot;dog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; TextureAtlas (
        &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;textures/dog_sprite_sheet.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;tile_size_x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;tile_size_y&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;columns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    )&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;File in &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; notation containing asset keys and their configurations. By default, the file ending should be &lt;code class=&quot;language-text&quot;&gt;.assets&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With such a &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; file, all configurations regarding your assets are assets themselves. A little setup is required to tell &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt; which &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; files should be loaded before which loading state.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add_plugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DefaultPlugins&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;AssetLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MyStates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetLoading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;continue_to_state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MyStates&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_asset_collection_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dynamic_asset_ron.assets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Setup to load asset configurations from a &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; file.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s it. We now moved all asset configuration out of our code.&lt;/p&gt;
&lt;h2 id=&quot;supported-extras&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#supported-extras&quot; aria-label=&quot;supported extras permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Supported extras&lt;/h2&gt;
&lt;p&gt;I showed examples for loading a texture atlas and &quot;simple&quot; files as dynamic assets. You can also load an image directly as a &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;StandardMaterial&lt;/span&gt;&lt;/code&gt; or load a complete directory as a vector of untyped handles. With dynamic assets it is also possible to have optional fields on asset collections. If the key cannot be resolved at run time, the field will be &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;None&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In some cases, it is required to give more information at compile time than just the key. For example, when loading a directory, the field needs to be additionally annotated with &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(folder)]&lt;/span&gt;&lt;/code&gt;. In the same way, optional fields need &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(optional)]&lt;/span&gt;&lt;/code&gt;. This is a limitation I have not been able to lift, because I need to handle return types that are different from &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt; separately at compile time.&lt;/p&gt;
&lt;h2 id=&quot;some-open-issues&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#some-open-issues&quot; aria-label=&quot;some open issues permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Some open issues&lt;/h2&gt;
&lt;p&gt;There are a few things I am not yet happy with. Admittedly, the API to load &lt;code class=&quot;language-text&quot;&gt;.assets&lt;/code&gt; files feels a bit clunky. One of the original ideas for this new functionality was that it should be easy to internationalize assets. If you have assets that need to be localized, you could configure them in different &lt;code class=&quot;language-text&quot;&gt;ron&lt;/code&gt; files and load them according to the user language. Currently, this would require the usage of internal APIs.&lt;/p&gt;
&lt;p&gt;I think the biggest missing feature at this point is an ergonomic way to define dependencies between asset collections. It should be possible to load &lt;code class=&quot;language-text&quot;&gt;.assets&lt;/code&gt; files as part of collections and tell &lt;a href=&quot;/projects/bevy_asset_loader&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;bevy_asset_loader&lt;/code&gt;&lt;/a&gt; to load and apply certain collections before others.&lt;/p&gt;
&lt;p&gt;There is also more design work to be done when thinking about different loading states in a &quot;real&quot; game. There are multiple szenarios in which a game would want to change the loaded assets. For example, localized assets need to be reloaded when the user changes the game language at run time. Even more common: the player might be switching between different levels requiring different asset collections to get loaded and unloaded.&lt;/p&gt;
&lt;p&gt;At the moment, all &lt;code class=&quot;language-text&quot;&gt;.assets&lt;/code&gt; files only get loaded and applied the first time a loading state is entered. If the same key gets overwritten in a later loading state, it&apos;s value would be wrong when re-entering the first loading state again.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Asset handling in Bevy apps]]></title><description><![CDATA[This post outlines the path that led to me writing bevy_asset_loader (repository), a plugin to simplify asset handling in Bevy applications…]]></description><link>https://www.nikl.me/blog/2021/asset-handling-in-bevy-apps/</link><guid isPermaLink="false">https://www.nikl.me/blog/2021/asset-handling-in-bevy-apps/</guid><pubDate>Fri, 06 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This post outlines the path that led to me writing bevy_asset_loader (&lt;a href=&quot;https://github.com/NiklasEi/bevy_asset_loader&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;repository&lt;/a&gt;), a plugin to simplify asset handling in &lt;a href=&quot;https://bevyengine.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy&lt;/a&gt; applications. The later part of the post is about current features of the plugin and thoughts on improvements.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;minimalistic-approach&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#minimalistic-approach&quot; aria-label=&quot;minimalistic approach permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Minimalistic approach&lt;/h2&gt;
&lt;p&gt;A first usage of assets in your Bevy game might look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;draw_the_player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Commands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    asset_server&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Res&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetServer&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; materials&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResMut&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Assets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ColorMaterial&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spawn_bundle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpriteBundle&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        material&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; materials&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;asset_server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;player.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;into&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpriteBundle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;A system that loads the player texture where and when it&apos;s needed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here we are telling the asset server to load &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;player.png&quot;&lt;/span&gt;&lt;/code&gt; from our &lt;code class=&quot;language-text&quot;&gt;assets&lt;/code&gt; directory. The actual loading of the file happens asynchronously. The &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;SpriteBundle&lt;/span&gt;&lt;/code&gt; will be in the ECS at the end of the current frame, but will not be rendered until the asset finished loading. As soon as the handle has the state &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;LoadState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loaded&lt;/span&gt;&lt;/code&gt;, our player texture will show up.&lt;/p&gt;
&lt;p&gt;In most games there will be a lot more assets than just one player texture. Probably, we would also have heroic background music, sound effects, and some fancy font to tell a story. If we load all of those assets when we need them, most will start loading in the first frame. Some might be ready after one frame, others will take longer. Imagine our first screen is build from different textures; the screen will render texture for texture over some duration. Not that nice...&lt;br&gt;
Loading handles where and when you need them is fine for small experiments, but most of the time, you want something more elaborate. All required assets should already be finished loading when they are used. Additionally, there should be an easy way to use the same handle at different points in the code.&lt;/p&gt;
&lt;h2 id=&quot;preparing-all-assets-in-a-loading-state&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#preparing-all-assets-in-a-loading-state&quot; aria-label=&quot;preparing all assets in a loading state permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Preparing all assets in a &quot;loading state&quot;&lt;/h2&gt;
&lt;p&gt;A common approach is to load all needed assets before starting the game. Most games have some sort of loading screen that gives them time to prepare their assets and indicates progress to the player.&lt;br&gt;
We can use &lt;a href=&quot;https://bevy-cheatbook.github.io/programming/states.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;states&lt;/a&gt; in Bevy to run a certain set of systems before our actual game logic runs. If we keep the loaded asset handles in resources, systems running during later states can use them through the ECS. The previous example might then look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// the asset is loaded in a previous state and &lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// TextureAssets is inserted as a resource&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   player&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ColorMaterial&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;draw_the_player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; commands&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Commands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    texture_assets&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Res&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;spawn_bundle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpriteBundle&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        material&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; texture_assets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SpriteBundle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;A system using a resource that contains the needed player material handle.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are three parts to implementing a &quot;loading state&quot; in Bevy.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start loading all required assets when entering the state&lt;/li&gt;
&lt;li&gt;Check the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;LoadState&lt;/span&gt;&lt;/code&gt; of all the assets on update&lt;/li&gt;
&lt;li&gt;When the state of all assets is &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;LoadState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loaded&lt;/span&gt;&lt;/code&gt;, build resources containing the handles, insert them, and change the state&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I will not show all of that code here. In case you want to implement this yourself, you can take a look at &lt;a href=&quot;https://bevy-cheatbook.github.io/cookbook/assets-ready.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;the cookbook&lt;/a&gt; for an example of checking asset loading state. You can of course also look at the &lt;a href=&quot;https://github.com/NiklasEi/bevy_asset_loader/blob/98c1bf91af98bef17b695e0e191c78105d00057a/bevy_asset_loader/src/lib.rs#L106-L136&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;implementation in bevy_asset_loader on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my first few Bevy projects I found myself copying and pasting the &quot;loading state&quot; plugin. Apart from the code duplication between projects, there were also a few other pain points. There was too much boilerplate when adding new resources or adding more handles to existing resources. In both cases the code had to be adjusted in several places.&lt;br&gt;
Another issue I had with my code at that point has to do with readability. When I see a piece of code that uses a handle from a resource, I want to be able to find the corresponding asset file path as fast as possible. With the initial implementation, the asset file paths where defined pretty far away from the resources, and it took multiple jumps in my IDE to get to them. The best case scenario would be to have the paths directly at their corresponding handle fields. This literally screams for some macro magic.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A perfect opportunity to write a Bevy plugin.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;introducing-bevy_asset_loader&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#introducing-bevy_asset_loader&quot; aria-label=&quot;introducing bevy_asset_loader permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Introducing bevy_asset_loader&lt;/h2&gt;
&lt;p&gt;The idea of bevy_asset_loader is to solve the above mentioned pain points and stop code duplication. There should be minimal boilerplate when adding new resources containing asset handles (from here on called asset collections) or adding new assets to an existing collection. At the same time, the asset path should be close to the resource field containing its handle.&lt;br&gt;
The current implementation of bevy_asset_loader delivers on all these points. An internal plugin that loads three asset collections during the state &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loading&lt;/span&gt;&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber 0&quot; class=&quot;language-rust line-numbers&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;LoadingPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadingPlugin&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;AssetLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Menu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;FontAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AudioAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;FontAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;fonts/FiraSans-Bold.ttf&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; fira_sans&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Font&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;AudioAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;audio/background.ogg&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; background&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AudioSource&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;audio/lost.ogg&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; lost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AudioSource&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/jar.png&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; jar&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Texture&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/shelf.jpg&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; shelf&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Texture&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;A Bevy plugin using bevy_asset_loader to load three different asset collections during the state &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loading&lt;/span&gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;All the boilerplate for the &quot;loading state&quot; is gone. Annotations directly at the fields contain the asset paths, keeping the internal name of the asset close to the file name. The derive macro currently works for structs with named fields. The field types need to be handles for assets that can be loaded directly from a file. This requires a one-to-one relationship of files to handles.&lt;/p&gt;
&lt;p&gt;For some use cases this is fine, but common assets like &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;/code&gt; need some extra steps. In Bevy, we can either build an atlas out of many textures (&lt;a href=&quot;https://github.com/bevyengine/bevy/blob/main/examples/2d/texture_atlas.rs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;example&lt;/a&gt;), or split a sprite sheet (&lt;a href=&quot;https://github.com/bevyengine/bevy/blob/main/examples/2d/sprite_sheet.rs&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;example&lt;/a&gt;). In both cases, we can load the initial textures like demonstrated above. The plugin currently cannot create the atlas out of the textures though. It would be nice to be able to use bevy_asset_loader to remove the boilerplate of creating the texture atlas and inserting a resource with the handle.&lt;/p&gt;
&lt;p&gt;In the last update, a small feature in this direction was added to bevy_asset_loader. The &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetLoader&lt;/span&gt;&lt;/code&gt; struct got an &lt;code class=&quot;language-text&quot;&gt;init_resource&lt;/code&gt; function, that can be used to initialize and insert a resource implementing &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FromWorld&lt;/span&gt;&lt;/code&gt;. The only difference of this function to Bevy&apos;s &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AppBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;init_resource&lt;/code&gt; is the timing of the initialisation. Bevy builds the resource on startup, while the &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetLoader&lt;/span&gt;&lt;/code&gt; will do it after inserting all our asset collections into the ECS. We can write a &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FromWorld&lt;/span&gt;&lt;/code&gt; implementation and retrieve our asset collections in it to, e.g., use the loaded sprite sheet to create a texture atlas.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre style=&quot;counter-reset: linenumber 0&quot; class=&quot;language-rust line-numbers&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;LoadingPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plugin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LoadingPlugin&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;AssetLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Loading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GameState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Menu&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with_collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RawTextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init_resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;RawTextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/cauldron.png&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; cauldron_sheet&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Texture&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; cauldron&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FromWorld&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;from_world&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;World&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; cell &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cell&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; raw_textures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cell
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RawTextureAssets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;RawTextureAssets not loaded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mut&lt;/span&gt; texture_atlases &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cell
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get_resource_mut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Assets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Could not get TextureAtlas assets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            cauldron&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; texture_atlases
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from_grid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                raw_textures&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cauldron_sheet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token class-name&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;192&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;192&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;line-numbers-rows&quot; style=&quot;white-space: normal; width: auto; left: 0;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Implementing &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FromWorld&lt;/span&gt;&lt;/code&gt; to create a texture atlas out of a sprite sheet. The asset loader will take care of initialising and inserting the resource.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This removes some boilerplate, but at the same time moves the asset path further away from the atlas handle. I am sure we can do better.&lt;/p&gt;
&lt;h2 id=&quot;future-improvements&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#future-improvements&quot; aria-label=&quot;future improvements permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Future improvements&lt;/h2&gt;
&lt;p&gt;The current state of the plugin already makes it a lot easier to prepare assets in a &quot;loading state&quot;. The support for common asset types like &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;/code&gt; could be improved though. I would like to extend the derive macro for &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetCollection&lt;/span&gt;&lt;/code&gt; to add more helper annotations. Maybe in the future it will be possible to create asset collections like below.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;rust&quot;&gt;&lt;pre class=&quot;language-rust&quot;&gt;&lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token attribute attr-name&quot;&gt;#[derive(AssetCollection)]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token type-definition class-name&quot;&gt;TextureAssets&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(texture_atlas(tile_size_x = 192., tile_size_y = 192., columns = 6, rows = 1))]&lt;/span&gt;
    &lt;span class=&quot;token attribute attr-name&quot;&gt;#[asset(path = &lt;span class=&quot;token string&quot;&gt;&quot;textures/sprite_sheet.png&quot;&lt;/span&gt;)]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; sprite&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TextureAtlas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Maybe in future versions of the library, texture atlases can be created with attributes on a derived &lt;code class=&quot;language-rust&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;AssetCollection&lt;/span&gt;&lt;/code&gt; (edit: this is possible since version 0.5.0).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I would also like to make it easier for users of bevy_asset_loader to build nice loading screens. There is an &lt;a href=&quot;https://github.com/NiklasEi/bevy_asset_loader/issues/6&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;open issue on GitHub asking for loading statistics&lt;/a&gt;, which should help a lot. The idea would be to offer a resource that keeps track of how many assets are currently loading, how many are done, and how many have not yet started to load. A system running during the &quot;loading state&quot; could then show a progress indicator based on this information.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thank you for reading! If you have any feedback, questions, or comments, you can find me at &lt;a href=&quot;https://bsky.app/profile/nikl.me&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;@nikl.me&lt;/a&gt; or on the &lt;a href=&quot;https://discord.gg/bevy&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;Bevy Discord server&lt;/a&gt; (@nikl).&lt;/p&gt;</content:encoded></item></channel></rss>