<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Android Dev Social]]></title><description><![CDATA[Android Dev Social]]></description><link>https://androiddev.blog/</link><image><url>https://androiddev.blog/favicon.png</url><title>Android Dev Social</title><link>https://androiddev.blog/</link></image><generator>Ghost 5.26</generator><lastBuildDate>Thu, 09 Apr 2026 13:43:32 GMT</lastBuildDate><atom:link href="https://androiddev.blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Best of AndroidDev.Social January 2023]]></title><description><![CDATA[<p>Every month Android Dev Social will send out links/posts we found interesting</p><p>Enjoy Best of January</p><h3 id="january-8th">January 8th</h3><p>ChatGPT Android by @skydoves gets a mention from the Google Developers Twitter account: <a href="https://androiddev.social/@skydoves/109654226719136891">https://androiddev.social/@skydoves/109654226719136891</a> and the Twitter link can be found here: <a href="https://twitter.com/googledevs/status/1612102389057593346">https://twitter.com/googledevs/status/</a></p>]]></description><link>https://androiddev.blog/monthly-news-january/</link><guid isPermaLink="false">63b9bd36c2ed120001822ed8</guid><dc:creator><![CDATA[Mike Nakhimovich]]></dc:creator><pubDate>Wed, 08 Feb 2023 23:13:23 GMT</pubDate><content:encoded><![CDATA[<p>Every month Android Dev Social will send out links/posts we found interesting</p><p>Enjoy Best of January</p><h3 id="january-8th">January 8th</h3><p>ChatGPT Android by @skydoves gets a mention from the Google Developers Twitter account: <a href="https://androiddev.social/@skydoves/109654226719136891">https://androiddev.social/@skydoves/109654226719136891</a> and the Twitter link can be found here: <a href="https://twitter.com/googledevs/status/1612102389057593346">https://twitter.com/googledevs/status/1612102389057593346</a></p><p>Drafting: Everyday Writing by @sen gets its 1.34 release. The drafting app gets some nice new features such as vibrating slightly when starting and finishing of moving custom toolbar icons and more. <a href="https://androiddev.social/@sen/109651286101040910">https://androiddev.social/@sen/109651286101040910</a></p><h3 id="january-9th">January 9th</h3><p>Mishaal Rahman posted a round-up of what&apos;s new in the Android 13 QPR2 Beta release that happened today. You can find that here: <a href="https://androiddev.social/@MishaalRahman/109661618551076877">https://androiddev.social/@MishaalRahman/109661618551076877</a> though it does lead directly to a Twitter list since threading on Mastodon is bit more work per Mishaal.</p><p>Great Post by spaghettiCode about how he wrangled a large codebase when starting new at a company</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://androiddev.social/@spaghetticode/109664588251010768"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ed :spaghettiCode: (@spaghetticode@androiddev.social)</div><div class="kg-bookmark-description">When I joined ASOS, the size of the codebase was initially quite overwhelming and I found it difficult to know exactly what screens I was looking at when navigating through the app &#x1F605; Thankfully, easy-dumpsys made this discovery process incredibly easy https://github.com/Kardelio/easy-dumpsys</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://androiddev.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Android Dev Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/androiddevsocial/accounts/avatars/109/274/064/617/073/674/original/e3b80255e6dabca8.png" alt></div></a></figure><h3 id="january-12th">January 12th</h3><p>Hosting Mastodon on DigitalOcean/Kubernetes</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://linuxlab.sh/@chuck/109677070266492583"><div class="kg-bookmark-content"><div class="kg-bookmark-title">socat TCP-LISTEN:1337 (@chuck@linuxlab.sh)</div><div class="kg-bookmark-description">The folks over at DO have contributed a &#x201C;running mastodon on kubernetes&#x201D; guide, using their hosted services :) Might be worth checking out if you&#x2019;re into managing infrastructure and thinking about starting your own instance. https://github.com/digitalocean/mastodon-on-kubernetes #mastoadmin #kub&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://linuxlab.sh/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">LinuxLab</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/linuxlab/accounts/avatars/000/000/001/original/f6f411cfed774f97.jpg" alt></div></a></figure><h3 id="january-16th">January 16th</h3><p>Our first guest blogger!</p><p>Article by Mark Murphy written about ActivityPub and Android: <a href="https://androiddev.blog/activitypub-and-android/">ActivityPub and Android (androiddev.blog)</a></p><h3 id="january-17th">January 17th</h3><p>The DIY&apos;ers of the Android community have delivered something that was quickly being requested by a lot of users. GoogleTV remote as a lock screen shortcut is available with AOSPMods. Original message here: <a href="https://androiddev.social/@MishaalRahman/109706513984072227">https://androiddev.social/@MishaalRahman/109706513984072227</a> and the GitHub link is here: <a href="https://github.com/siavash79/AOSPMods/commit/8bd10effae2f9eb4a3cebcf4d4360f44f62dc5a6">CHANGELOG: Added TV remote to lockscreen shortcuts &#xB7; siavash79/AOSPMods@8bd10ef (github.com)</a></p><p>Compose Compiler 1.4.0 released with Kotlin 1.8.0 support. <a href="https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.4.0">Compose Compiler &#xA0;| &#xA0;Android Developers</a></p><h3 id="january-19th">January 19th</h3><p>Twitter officially puts a stop to all 3rd party Twitter apps from using the API: <a href="https://www.theverge.com/2023/1/19/23562947/twitter-third-party-client-tweetbot-twitterific-ban-rules">Twitter officially bans third-party clients with new developer rules - The Verge</a></p><h3 id="january-21st">January 21st</h3><p>Security researcher Ken Gannon discloses two exploits that impact Samsung&apos;s app store on Android devices. <a href="https://androiddev.social/@MishaalRahman/109723958464120653">https://androiddev.social/@MishaalRahman/109723958464120653</a></p><p>Google possibly to unveil about 20 new products and a new version of Google search at Google I/O and later this year: <a href="https://androiddev.social/@MishaalRahman/109722458643758585">https://androiddev.social/@MishaalRahman/109722458643758585</a> original article here: <a href="https://www.nytimes.com/2023/01/20/technology/google-chatgpt-artificial-intelligence.html">Google Calls In Larry Page and Sergey Brin to Tackle ChatGPT and A.I. Chatbots - The New York Times (nytimes.com)</a></p><p>Dual eSIM support appears to start working for some Pixel 7 users: <a href="https://blog.esper.io/pixel-7-features-android-13/#android-13-apis-that-hint-at-pixel-tablet-features:~:text=to%20your%20phone.%E2%80%9D-,Dual%20eSIM%20support,-The%20Google%20Play">The Pixel 7 features powered by Android 13 APIs (esper.io)</a></p><h3 id="january-22nd">January 22nd</h3><p>Google lays off 12,000+ employees, some who had been with the company for 15 - 20+ years! <a href="https://www.cnbc.com/2023/01/21/google-employees-scramble-for-answers-after-layoffs-hit-long-tenured.html">Google employees scramble for answers after layoffs hit long-tenured (cnbc.com)</a></p><p>Microsoft lays off all of it&apos;s VR/AR teams along with about 11,000 employees. <a href="https://www.extremetech.com/electronics/342480-microsoft-lays-off-all-of-its-vr-mixed-reality-and-hololens-employees">Microsoft Lays Off All Its VR, Mixed Reality, and HoloLens Employees: Report - ExtremeTech</a></p><h3 id="january-23rd">January 23rd</h3><p>Microsoft invests $10 billion into Open AI after laying off 11,000+ employees. <a href="https://www.bloomberg.com/news/articles/2023-01-23/microsoft-makes-multibillion-dollar-investment-in-openai">Microsoft to Invest $10 Billion in ChatGPT Maker OpenAI (MSFT) - Bloomberg</a></p><h3 id="january-25th">January 25th</h3><p>Grocy 3.0 for Android is released with a Material You redesign. Grocy is a Grocery Management app by Patrick Zedler. <a href="https://androiddev.social/@patzly/109752341647865930">https://androiddev.social/@patzly/109752341647865930</a></p><p>Gradle folks talk about how Minecraft and suspicious gradle wrappers :eyes:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://mastodon.social/@eskatos/109749538684281437"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Paul Merlin &#x1F37F; (@eskatos@mastodon.social)</div><div class="kg-bookmark-description">On January 11th 2023, #gradle was contacted by MinecraftOnline about two unusual and suspicious Gradle wrapper JARs found in some of their repositories. Read about the story herehttps://blog.gradle.org/wrapper-attack-report Read about how to protect yourself against such #supplychainattacks here&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://static-cdn.mastodon.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Mastodon</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://files.mastodon.social/accounts/avatars/109/245/169/646/334/770/original/2735b1061dfe6bb5.png" alt></div></a></figure><p></p><p>Updates to Sceneview from our Arcore friends</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://androiddev.social/@Kents/109750222165440793"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Kent Sinclair :androidEyes: (@Kents@androiddev.social)</div><div class="kg-bookmark-description">pretty sweet update to SceneView library last week enabling compose support maintained by Thomas Gorrise https://github.com/SceneView/sceneview-android</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://androiddev.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Android Dev Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/androiddevsocial/accounts/avatars/109/294/498/529/082/611/original/5fb1891b32af536d.jpeg" alt></div></a></figure><p>How to connect to localhost from android emulator</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://androiddev.social/@Jeroenmols/109749532076281170"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Jeroen Mols (@Jeroenmols@androiddev.social)</div><div class="kg-bookmark-description">Attached: 1 image Blogged: Connecting your Android emulator to a local development server. Neat little trick with etc/hosts that allows the emulator to access local IP addresses. &#x1F449; https://jeroenmols.com/blog/2023/01/25/development-server-emulator/ #AndroidDev #server #react</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://androiddev.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Android Dev Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/androiddevsocial/media_attachments/files/109/749/531/084/088/550/original/6e689a4a5a102fd0.jpg" alt></div></a></figure><h3 id="january-26th">January 26th</h3><p>Mobile dev folks &#xA0;and Maestro are continuing to kill it</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://androiddev.social/@aalaniz/109756214163941137"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Aaron Alaniz (@aalaniz@androiddev.social)</div><div class="kg-bookmark-description">The folks at Maestro continue to amaze me. This latest change opens up a new set of product owners to contribute to automated testing. I also love the focus on maintenance a core tenant. https://blog.mobile.dev/announcing-maintainable-no-code-ui-automation-for-mobile-with-maestro-studio-16bb0c42da3b</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://androiddev.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Android Dev Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/androiddevsocial/accounts/avatars/109/552/705/528/989/178/original/b18ebe5d6a4fd0d9.png" alt></div></a></figure><h3 id="january-27th">January 27th</h3><p>Trunks, another new app for Mastodon, gets an Android and iOS beta release with features such as: Multi-account support, quote toots, threaded replies, dark mode and instance browse. <a href="https://mstdn.social/@trunksapp/109759970359987018">https://mstdn.social/@trunksapp/109759970359987018</a> and the Play Store link: <a href="https://play.google.com/store/apps/details?id=com.decad3nce.trunks">trunks - Apps on Google Play</a></p><p>Ben&apos;s journey to repair an iphone using 3 50lb pelican cases worth of tools</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://androiddev.social/@benlikestocode/109763984065397052"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ben Oberkfell (@benlikestocode@androiddev.social)</div><div class="kg-bookmark-description">Phone looks good as new, although it was so tedious ripping out that glue that I debated just tossing this and running to the mall for a new phone. All in all what I learned from this is that this was an incredible pain in the ass. Under no circumstances should anyone do this. I&#x2019;m certain an App&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://androiddev.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Android Dev Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.masto.host/androiddevsocial/accounts/avatars/109/274/937/350/976/876/original/4f255571686a11c7.jpeg" alt></div></a></figure><h3 id="january-30th">January 30th</h3><p>You can now sign up for Google&apos;s new accessibility newsletter that should be sent out quarterly. <a href="https://docs.google.com/forms/d/e/1FAIpQLSemxYwYO_LRzm6Z6oI1Rxv5_cqEMv6_7JY2uo-1UWDuqlbzxA/viewform">Sign up for Google&apos;s Accessibility Newsletter</a></p><p>Compiling for OSX without a mac</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://snapp.social/@alsutton/109777531328204119"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Al Sutton (@alsutton@snapp.social)</div><div class="kg-bookmark-description">This looks like an interesting way to get around needing a physical m1 mac for testing... https://github.com/sickcodes/Docker-OSX</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://snapp.social/packs/media/icons/apple-touch-icon-1024x1024-db6849588b44f525363c37b65ef0ac66.png" alt><span class="kg-bookmark-author">Snapp.Social</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://s3-eu-west-2.amazonaws.com/snapp-social-files/accounts/avatars/109/280/595/953/486/138/original/9ccdff1adf9699cf.png" alt></div></a></figure><p></p><h3 id="polls-by-cliff-wade">Polls by Cliff Wade</h3><p><a href="https://androiddev.social/@cliffwade/109740641442401729">https://androiddev.social/@cliffwade/109740641442401729</a></p><p><a href="https://androiddev.social/@cliffwade/109734302398374356">https://androiddev.social/@cliffwade/109734302398374356</a></p><p><a href="https://seerofsouls.xyz/@Cliff/109716292458502438">https://seerofsouls.xyz/@Cliff/109716292458502438</a></p><p><a href="https://seerofsouls.xyz/@Cliff/109705812255921808">https://seerofsouls.xyz/@Cliff/109705812255921808</a></p><p></p><p>Thanks folks, feel free to ping @friendlymike or @cliffwade on androiddev.social if any you&apos;d like us to add any new links to February&apos;s newsletter</p><p>Big thank you to Cliff Wade for gathering the above material &lt;3 </p>]]></content:encoded></item><item><title><![CDATA[Interface Naming Conventions]]></title><description><![CDATA[<p></p><p>Many engineers will tell you that one of the most complicated responsibilities of our job is naming things. Variables, classes, functions, everything we write requires conscious thought.</p><p>A special case among these are interfaces. This is because we not only have to name an interface, but we need to decide</p>]]></description><link>https://androiddev.blog/interface-naming-conventions/</link><guid isPermaLink="false">63e128cafdd9390001eb8eba</guid><dc:creator><![CDATA[Adam McNeilly]]></dc:creator><pubDate>Tue, 07 Feb 2023 18:09:47 GMT</pubDate><content:encoded><![CDATA[<p></p><p>Many engineers will tell you that one of the most complicated responsibilities of our job is naming things. Variables, classes, functions, everything we write requires conscious thought.</p><p>A special case among these are interfaces. This is because we not only have to name an interface, but we need to decide how to name the implementations as well.</p><hr><h2 id="basic-convention">Basic Convention</h2><p>Traditionally, and by this I mean in the textbooks I read in college, interfaces and their implementations shared the same naming convention. I&apos;ve seen this surfaced in one of two ways.</p><ul><li>Using an <code>IInterface</code> and <code>Implementation</code> convention:</li></ul><pre><code class="language-kotlin">interface IBookRepository {
    // ...
}

class BookRepository {
    // ...
}
</code></pre><ul><li>Using an <code>Impl</code> suffix:</li></ul><pre><code class="language-kotlin">interface BookRepository {
    // ...
}

class BookRepositoryImpl {
    // ...
}
</code></pre><p>This pattern often stems from before Integrated Development Environments (IDEs), where we had extra tools to differentiate between two classes. This naming convention gave us a quick way to scan files and see the difference between the two.</p><h2 id="problems">Problems</h2><p>This approach, while it does clearly separate the convention between an interface and its implementation, has a couple inherent problems.</p><ol><li>It assumes one implementation per interface. If we write a <code>BookRepository</code> and <code>BookRepositoryImpl</code>, but need to add a second, different <code>BookRepository</code> what do we call it? <code>BookRepositoryImpl2</code>?</li><li>Similarly, the <code>Impl</code> suffix doesn&apos;t provide any information about how the implementation operates. Is it pulling local data? Remote data? What is the data source used by <code>BookRepositoryImpl</code>? All of these questions require active investigation and diving into the code to understand and come back with a confident answer.</li></ol><h2 id="alternatives">Alternatives</h2><p>To avoid these problems, we can consider a number of alternative naming conventions for our interfaces and their implementations. As always, we have multiple different solutions, that you may choose to stick to one, mix and match, or change based on your situation. I&apos;ve decided to highlight a few different approaches I have tried myself, and have heard used by others. Have different ideas? Let me know in the comments!</p><h3 id="naming-with-data-source">Naming With Data Source</h3><p>If our application has a <code>BookRepository</code> data source interface, the implementation can be named based on the data source used to request books. Some examples may look like this:</p><pre><code class="language-kotlin">interface BookRepository

class GoogleBooksBookRepository : BookRepository

class OpenLibraryBooksRepository : BookRepository

class NewYorkTimesBookRepository : BookRepository
</code></pre><h3 id="naming-with-situation">Naming With Situation</h3><p>Sometimes, our implementation might always be specific to the same data source/dependency. We still may want to leverage interfaces because it helps with testing, or some other situation. In these moments, we can consider naming our interface based on the situation it is being used in. For example, consider we include some interface for Crashlytics initialization. In our production app, we can name it accordingly.</p><pre><code class="language-kotlin">interface CrashlyticsInitializer

class ProdCrashlyticsInitializer : CrashlyticsInitializer
</code></pre><p>Why is this better than <code>CrashlyticsInitializerImpl</code>? I believe this is better because it begins to set a precedent for other situations we might use a <code>CrashlyticsInitializer</code>. Situations like our device tests, where we can have a <code>TestCrashlyticsInitializer</code> or a special debug flavor that includes a <code>DebugCrashlyticsInitializer</code>.</p><h3 id="naming-with-responsibility">Naming With Responsibility</h3><p>Another way to look at this is to think of the reponsibility of a class. Perhaps your repository combines other data sources, with an offline first preference. You may consider something like this:</p><pre><code class="language-kotlin">interface BookRepository {
    // ...
}

class OfflineFirstBookRepository(
    private val localRepository: BookRepository,
    private val remoteRepository: BookRepository,
) : BookRepository { 
    // ...
}
</code></pre><h3 id="other-considerations">Other Considerations</h3><p>The above suggestions are my personal preference, but there are other situations you may want to consider. By prefixing the implementations, searching for them in the IDE becomes slightly less straight forward. When searching in Android Studio, for example, if we start typing <code>BookRepository</code>, any files that are named <code>BookRepositoryImpl</code> get included in the same search. For that reason, you may consider taking the above suggestions, and combining them with the traditional suffix, like this:</p><pre><code class="language-kotlin">interface BookRepository

class BookRepositoryGoogleBooksImpl : BookRepository

class BookRepositoryOpenLibraryImpl : BookRepository

class BookRepositoryNewYorkTimesImpl : BookRepository
</code></pre><h2 id="recap">Recap</h2><p>At the end of the day, the naming convention you and your team use is a personal choice. By using interfaces, we&apos;re making a good choice toward scalable applications. The naming convention is mostly cosmetic, but I hope you&apos;ve seen that it can serve a purpose for discoverability and quickly understanding the codebase by reading class names. What I believe matters most is that you take the time to think about it. Your conversation may ultimately end with &quot;well, there&apos;s no dependency or situation specific usage here, so we just want to fall back on the <code>Impl</code> suffix. That&apos;s okay! Not all suggestions apply to all situations, but hopefully this post helps you find ways to provide more clarity in class names.</p>]]></content:encoded></item><item><title><![CDATA[The "trigger" pattern]]></title><description><![CDATA[<p>Reactive programming and the observable pattern are everywhere nowadays. It is so omnipresent that it&apos;s hard to find a library which does not use it or enforce it.</p><p>It is even more so in the Android ecosystem where Google first released their <a href="https://developer.android.com/topic/libraries/architecture/livedata"><code>LiveData</code></a> library in <a href="https://android-developers.googleblog.com/2017/11/announcing-architecture-components-10.html">2017</a> and is</p>]]></description><link>https://androiddev.blog/the-trigger-pattern/</link><guid isPermaLink="false">63e104cafdd9390001eb8e6b</guid><dc:creator><![CDATA[Benjamin Monjoie]]></dc:creator><pubDate>Mon, 06 Feb 2023 14:45:50 GMT</pubDate><media:content url="https://androiddev.blog/content/images/2023/02/50785385016_c2da3a2779_k.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://androiddev.blog/content/images/2023/02/50785385016_c2da3a2779_k.jpg" alt="The &quot;trigger&quot; pattern"><p>Reactive programming and the observable pattern are everywhere nowadays. It is so omnipresent that it&apos;s hard to find a library which does not use it or enforce it.</p><p>It is even more so in the Android ecosystem where Google first released their <a href="https://developer.android.com/topic/libraries/architecture/livedata"><code>LiveData</code></a> library in <a href="https://android-developers.googleblog.com/2017/11/announcing-architecture-components-10.html">2017</a> and is now more and more supporting Kotlin&apos;s <a href="https://developer.android.com/kotlin/flow">Flow</a> in most of their libraries.</p><p>In this article, I would like to shed some light on a pattern which I first encountered in <a href="https://github.com/android/architecture-components-samples/tree/master/GithubBrowserSample">Google&apos;s GitHub Browser Sample</a> which I call the <strong>trigger pattern</strong>. It&apos;s been mentioned <a href="https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54">here</a> and <a href="https://bladecoder.medium.com/to-implement-a-manual-refresh-without-modifying-your-existing-livedata-logic-i-suggest-that-your-7db1b8414c0e">there</a> on the web but without a proper explanation.</p><h2 id="what-is-the-trigger-pattern">What is the Trigger pattern?</h2><p>The idea is to use an <code>observable</code> source to trigger a chain of calls which will result in the state you want your observer to have.</p><p>The Trigger pattern takes advantage of the Observable pattern, it allows you to chain operations to create the state of your <code>observable</code>s</p><p>Here is an example with <code>Flow</code>s:</p><pre><code class="language-kotlin">interface Repository {
   suspend fun retrieveSomeInfoForId(id: String): Result&lt;Info&gt;
}

private val trigger = MutableSharedFlow&lt;String&gt;(replay = 1)
val state: Flow&lt;Result&lt;Info&gt;&gt; = trigger.mapLatest { id -&gt; 
    repository.retrieveSomeInfoForId(id) 
}
</code></pre><p><em>You can see a real example in <a href="https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/ui/user/UserViewModel.kt">Google&apos;s GitHub Browser Sample&apos;s ViewModel</a></em></p><h2 id="so-what-does-it-do-exactly">So what does it do exactly?</h2><p>Everytime you push something inside <code>trigger</code> (E.g.: <code>trigger.emit(&quot;123&quot;)</code>) this will execute <code>repository.retrieveSomeInfoForId</code> and each values emitted by the <code>Flow</code> returned by <code>retrieveSomeInfoForId</code> will be emitted by <code>state</code> as well.</p><h2 id="how-is-that-useful">How is that useful?</h2><p>Let&apos;s take an example of something you might have seen in some code bases where we have a screen that display some information about an <code>id</code> it received as a parameter:</p><pre><code class="language-kotlin">interface Repository {
   suspend fun retrieveSomeInfoForId(id: String): Result&lt;Info&gt;
}

private val _info = MutableSharedFlow&lt;Result&lt;Info&gt;&gt;(replay = 1)
val info: Flow&lt;Result&lt;Info&gt;&gt; = _info

fun loadInfo(id: String) = viewModelScope.launch {
    _info.emit(repository.retrieveSomeInfoForId(id))
}
</code></pre><p>And now, consider what would happen if you call <code>loadInfo</code> &#xA0;twice, each time with a different <code>id</code>. What if the second call finishes quicker than the first one?<br>And what if you want to do something with the info and push the result in a different <code>Flow</code>?</p><pre><code class="language-kotlin">interface Repository {
   suspend fun retrieveSomeInfoForId(id: String): Result&lt;Info&gt;
   suspend fun getSimilarNames(info: Info): List&lt;String&gt;
}

private val _info = MutableSharedFlow&lt;Result&lt;Info&gt;&gt;(replay = 1)
val info: Flow&lt;Result&lt;Info&gt;&gt; = _info

private val _similarNames = MutableSharedFlow&lt;List&lt;String&gt;&gt;(replay = 1)
val similarNames: Flow&lt;List&lt;String&gt;&gt; = _similarNames

fun loadInfo(id: String) = viewModelScope.launch {
    val result = repository.retrieveSomeInfoForId(id)
    _info.emit(result)
    _similarNames.emit(emptyList())
    result.onSuccess {
       _similarNames.emit(repository.getSimilarNames(it))
    }
}
</code></pre><p>Again, if you call <code>loadInfo</code> twice, you could get into some inconsistent states and now the logic of fetching similar names is entangled with the logic of retrieving the <code>Info</code>.</p><h2 id="how-could-we-make-this-code-better">How could we make this code better?</h2><p>With the trigger pattern, of course! Let&apos;s rewrite it and see how this would look like:</p><pre><code class="language-kotlin">interface Repository {
   suspend fun retrieveSomeInfoForId(id: String): Result&lt;Info&gt;
   suspend fun getSimilarNames(info: Info): List&lt;String&gt;
}

private val id = MutableSharedFlow&lt;String&gt;(replay = 1)

val info: Flow&lt;Result&lt;Info&gt;&gt; = id.mapLatest { id -&gt;
    repository.retrieveSomeInfoForId(id)
}

val similarNames: Flow&lt;List&lt;String&gt;&gt; = info.transformLatest { result -&gt;
    emit(emptyList())
    result.onSuccess {
       emit(repository.getSimilarNames(it))
    }
}

fun loadInfo(id: String) = viewModelScope.launch {
    this.id.emit(id)
}
</code></pre><p>Let&apos;s look a little bit closer at what happens here. We call <code>loadInfo</code> which launches a coroutine to emit into the trigger (the <code>MutableSharedFlow</code> called <code>id</code>) the id of the resource to load.<br>This, in turns, launches the transformation of <code>info</code> to retrieve the information we are looking for.<br>When <code>info</code> emits, it will also launch the transformation of <code>similarNames</code>.</p><p>As you can see, the code is not all cramped up into one method anymore. Also, each new <code>emit</code> will cancel the previous transformations, without the need for us to keep a reference to them and canceling them when receiving a new <code>id</code>, thanks to the <code>mapLatest</code> and <code>transformLatest</code> methods. This makes this less error prone.</p><h1 id="after-thoughts">After thoughts</h1><h2 id="new-subscribers-and-resubscribes">New subscribers and resubscribes</h2><p>This post is focusing on the concept of emitting a value into an <code>observable</code> which then &apos;triggers&apos; some logic into other <code>observable</code>s. Yet you may have noticed that those <code>observable</code>s would execute their transformation multiple times when some new subscriber subscribed. It&apos;s often not what we want. In that case, it&apos;s a good idea to use <a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/share-in.html">shareIn</a> and <a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/state-in.html">stateIn</a> but I will not go into details about those as there are already plenty of good articles about <code>SharedFlow</code>s and <code>StateFlow</code>s.</p><h2 id="emit-and-the-execution-order"><code>emit</code> and the execution order</h2><p>It is important to note that using this method, calling <code>loadInfo(id: String)</code> another time won&apos;t cancel the previous unfinished coroutines (the lambda in <code>viewModelScope.launch</code>, I.e. the call to <code>emit</code>). Therefore, the order of the calls to <code>emit</code> into the trigger is important. When using <code>viewModelScope.launch</code>, you are actually using the <code>Dispatchers.Main.immediate</code> so the coroutines will be queued and it will not be a problem, unless you suspend before the call to <code>emit</code>. This problem is also present with the code we have improved but it is far less likely to append with the trigger pattern as we tend to not have any logic into the &quot;triggering&quot; methods.</p>]]></content:encoded></item><item><title><![CDATA[You don't need a custom share sheet for that!]]></title><description><![CDATA[<h4 id="native-share-sheets-have-come-a-long-way-on-android-and-theres-really-no-excuse-for-implementing-custom-share-sheets-anymore-right">Native share sheets have come a long way on Android and there&apos;s really no excuse for implementing custom share sheets anymore. Right?</h4><p>At Pocket we sat down a couple years ago to retire our old custom share UI and switch to the native one, but we uncovered two</p>]]></description><link>https://androiddev.blog/native-share-sheet-customisation/</link><guid isPermaLink="false">63dba947fdd9390001eb8e11</guid><dc:creator><![CDATA[Marcin Koziński]]></dc:creator><pubDate>Thu, 02 Feb 2023 20:40:45 GMT</pubDate><content:encoded><![CDATA[<h4 id="native-share-sheets-have-come-a-long-way-on-android-and-theres-really-no-excuse-for-implementing-custom-share-sheets-anymore-right">Native share sheets have come a long way on Android and there&apos;s really no excuse for implementing custom share sheets anymore. Right?</h4><p>At Pocket we sat down a couple years ago to retire our old custom share UI and switch to the native one, but we uncovered two product requirements that we thought would block us:</p><ul><li>We wanted to customise the text we included in the share intent based on the target app.</li><li>We wanted to track which apps our users choose.</li></ul><p>Fortunately we dug into the documentation and discovered that both of these are possible with the native share sheet! Let me show you how!</p><h2 id="basic-share-sheet-intent">Basic share sheet intent</h2><p>But first, so we&apos;re all on the same page, here&apos;s our starting point&#x2014;a way to create a basic native share sheet intent without any customisation:</p><pre><code class="language-kotlin">fun createBasicShareIntent(context: Context): Intent {
    val sendIntent = Intent(Intent.ACTION_SEND).apply {
        type = &quot;text/plain&quot;
        putExtra(Intent.EXTRA_SUBJECT, &quot;Subject&quot;)
        putExtra(Intent.EXTRA_TEXT, &quot;Hello world!&quot;)
        addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
        addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
    }
    return Intent.createChooser(sendIntent, null)
}
</code></pre><h2 id="per-app-customisation-with-extrareplacementextras">Per-app customisation with <code>EXTRA_REPLACEMENT_EXTRAS</code></h2><p>Intent extras are an essential part of talking to the Android framework. This API goes the <em>extra</em> mile and lets you provide extra extras. No, really, meet <a href="https://developer.android.com/reference/kotlin/android/content/Intent#extra_replacement_extras">Intent.EXTRA_REPLACEMENT_EXTRAS</a>.</p><p>It lets you attach a <code>Bundle</code> of replacements to a chooser intent. <code>Bundle</code> is essentially a map. In this case the keys are package names of the target apps you want customised share text for. And values are <code>Bundle</code>s of intent extras you want to add to or replace in the base intent. Yes, it&apos;s a <code>Bundle</code> of <code>Bundle</code>s.</p><p><em>inception theme plays</em></p><p>Putting it together, here&apos;s how you would send a custom message to the official Twitter app:</p><pre><code class="language-kotlin">fun createShareIntentWithCustomMessage(context: Context): Intent {

    // snip

    val replacements = Bundle().apply {
        putBundle(
            &quot;com.twitter.android&quot;,
            Bundle().apply {
                putString(Intent.EXTRA_TEXT, &quot;Bye Elon!&quot;)
            },
        )
    }
    return Intent.createChooser(sendIntent, null).apply {
        putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements)
    }
}
</code></pre><p>You can call <code>putBundle()</code> multiple times, to provide (same or different) replacements for more than one app.</p><p>Note: this was added in API 21, which should be low enough these days to just use directly in any modern app. If you&apos;re stuck on a lower <code>minSdkVersion</code>, well.. here&apos;s an argument I would try to make in front of my team: &quot;It isn&apos;t worth it to roll an entire custom share sheet just to support a custom message for users on a 9+ year old Android version.&quot;</p><h2 id="tracking-share-sheet-clicks">Tracking share sheet clicks</h2><p>Above we used a simple <code>Intent.createChooser()</code> call that accepts an intent and an optional title. But there&apos;s an <a href="https://developer.android.com/reference/kotlin/android/content/Intent#createchooser_1">Intent.createChooser(intent, title, intentSender)</a> overload added in API 22, which accepts an optional <code>IntentSender</code>. It&apos;s basically a (pretty convoluted) way to add a callback when a user chooses an app from the sheet.</p><p>The callback lets you check the chosen app&apos;s package name and do anything you want with it. In the example below I&apos;ll call a (fake) analytics service to track the usage. Another use case&#x2014;suggested by the docs&#x2014;is remembering the last chosen app so you can provide it as a one touch share shortcut in your UI.</p><p>There&apos;s a couple of steps. First we need to create our &quot;callback&quot; in the form of a broadcast receiver that checks <a href="https://developer.android.com/reference/kotlin/android/content/Intent#extra_chosen_component">Intent.EXTRA_CHOSEN_COMPONENT</a>. Here&apos;s an example receiver (you can name the class anything you want):</p><pre><code class="language-kotlin">class ChosenComponentReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val component = IntentCompat.getParcelableExtra(
            intent,
            Intent.EXTRA_CHOSEN_COMPONENT,
            ComponentName::class.java
        )
        component?.let {
            analytics.track(it.packageName)
        }
    }
}
</code></pre><p>The second step is passing an intent sender that points to this broadcast receiver when creating the share sheet intent:</p><pre><code class="language-kotlin">fun createShareIntentWithCallback(context: Context): Intent {

    // snip

    val pendingIntent = PendingIntent.getBroadcast(
        context,
        REQUEST_CODE,
        Intent(context, ChosenComponentReceiver::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
    )
    return Intent.createChooser(sendIntent, null, pendingIntent.intentSender)
}
</code></pre><p>It&apos;s not the most straightforward of things, but once you&apos;re done with the special ceremony required by the framework, it works like a normal callback or rather a normal broadcast receiver.</p><h2 id="conclusion">Conclusion</h2><p>If you, your co-workers, your boss or&#x2014;worse yet&#x2014;your client thought you&apos;ll have to invest in building a custom share sheet, because otherwise there&apos;s no way to customise the shared text on a per-app basis or no way to track where your users share to, then you can be the hero that explains it is possible to save all this engineering and design effort and satisfy your requirements while showing the users a polished, familiar share experience.</p><p>Clients and bosses can be very creative, so there still might be some other reasons to implement a custom share sheet. But luckily the Android team handled at least these two common cases in the native one, which should greatly reduce how often you don&apos;t have a choice and have to go with a custom implementation. Thanks Android team!</p><p>You can see <a href="https://github.com/marcin-kozinski/blog/blob/main/2023-02-02-native-share-sheet-customisation/NativeShareSheetCustomizations.kt">complete code examples</a> from this post and the <a href="https://github.com/marcin-kozinski/blog/blob/main/2023-02-02-native-share-sheet-customisation/README.md">source of the post itself</a> on Github (if you see anything from glaring errors to typos, please send me a PR!). If you have any questions or comments, reply to this <a href="https://androiddev.social/@marcin/109797080796059739">toot</a>.</p>]]></content:encoded></item><item><title><![CDATA[ActivityPub and Android]]></title><description><![CDATA[Once upon a time, Android was a "green field opportunity", and now is a mature technology. Perhaps ActivityPub is another area of opportunity, where Android developers can be distinctive.]]></description><link>https://androiddev.blog/activitypub-and-android/</link><guid isPermaLink="false">63c55560c2ed120001822f8b</guid><dc:creator><![CDATA[Mark Murphy]]></dc:creator><pubDate>Mon, 16 Jan 2023 15:42:14 GMT</pubDate><content:encoded><![CDATA[<p>About 14 years ago, I started learning Android. Shortly thereafter, I wound up as a <a href="https://commonsware.com">freelance Android developer advocate</a>. Part of what I did in the early years was try to convince developers that Android had business potential. To that end, I wrote <a href="https://www.androidguys.com/tips-tools/coding/android-business-models/">lots of blog posts</a> and delivered <a href="https://www.slideshare.net/commonsguy/making-money-at-mobile-60-business-models?qid=38ad1a3c-b59d-4e3b-853c-47d1903fb61b&amp;v=&amp;b=&amp;from_search=5">presentations</a> outlining different opportunities in Android. My hope was that if we built the apps, there would be enough interest in Android that it would carve out a stable chunk of the mobile OS market and be a major piece of foundation technology.</p><p>That turned out well, I think.</p><p>A long-term side-effect, though, is that Android is now a mature OS with a mature ecosystem. In 2012, Android itself was still a &quot;green field opportunity&quot;. In 2022... not so much. You can build a business around Android, but you need something more than Android. Similarly, being an Android app developer is <em>useful</em> but is not <em>distinctive</em>. There are lots of Android app developers, and you need to do something beyond that to &quot;stand out from the crowd&quot;.</p><p>If I were a younger developer looking to be distinctive, or I were looking to start my fourth(!) business, I&apos;d be looking into <a href="https://en.wikipedia.org/wiki/ActivityPub">ActivityPub</a>.</p><hr><p>This blog is associated with <a href="https://androiddev.social/">androiddev.social</a>, a Mastodon instance catering to Android app developers. Mastodon is gaining significant interest due to the issues surrounding Twitter. Mastodon is an app, an ecosystem, and an example of ActivityPub.</p><p>ActivityPub is <a href="https://www.w3.org/TR/activitypub/">a W3C specification</a> for data exchange, focusing on social network-style apps. In particular, ActivityPub is designed around a &quot;federated&quot; set of servers, where the social network is not merely a network of <em>users</em>, but a network of <em>sites</em> that exchange data. The relationship between Mastodon and ActivityPub is roughly analogous to the relationship between podcasting and RSS: RSS says &quot;this is a feed of content&quot;, while podcasting says &quot;the content is metadata about podcasts and episodes&quot;.</p><p>ActivityPub is more than just Mastodon, though. If &quot;Mastodon is Twitter, but using ActivityPub&quot;, then:</p><ul><li><a href="https://friendi.ca/">Frendica</a> might be Facebook, but using ActivityPub</li><li><a href="https://pixelfed.org/">PixelFed</a> might be Instagram, but using ActivityPub</li><li>And <a href="https://codeberg.org/fediverse/delightful-fediverse-apps">much, much more</a></li></ul><p>To me, ActivityPub has some &quot;Android in 2010&quot; vibes. There is a solid foundation along with early success stories, but there are few major players, none of whom are likely to completely dominate the space. While ActivityPub will lack the push of interested parties like Google and Samsung had for Android, its interoperable nature means that the success of things like Mastodon can help propel the entire ActivityPub ecosystem.</p><hr><p>So, what could an Android app developer do who wants to get involved in ActivityPub? There are <em>lots</em> of possibilities, including:</p><ul><li>You could contribute to Mastodon client libraries, such as <a href="https://github.com/outadoc/mastodonk">mastodonk</a> or <a href="https://github.com/sys1yagi/mastodon4j">mastodon4j</a>. Perhaps start small and useful (test cases! documentation!) and contribute in a more serious fashion as you gain comfort and acceptance.</li><li>You could contribute to open source Mastodon clients, whether that is <a href="https://github.com/mastodon/mastodon-android">the official client</a> or an independent one like <a href="https://github.com/tuskyapp/Tusky">Tusky</a>. Again, perhaps start small and grow your involvement over time.</li><li>You could move up the stack a bit and contribute to open source ActivityPub clients, such as <a href="https://fedilab.app/wiki/home/">Fedilab</a>.</li><li>You could create your own Mastodon or ActivityPub app from scratch, or fork an existing project and head off in your own direction.</li><li>You could create your own Mastodon or ActivityPub library, perhaps targeting use cases or language bindings (Kotlin/Multiplatform? Flutter? React Native?) that you feel are under-served.</li><li>You could write a library aimed at generic consumption of feeds, whether those are ActivityPub, RSS, Atom, GNU Social, ActivityStreams, or something else. While this library might wind up being focused purely on reading (rather than posting), it could open up possibilities for more apps to support more protocols.</li><li>You could create an ActivityPub client aimed at a specific form factor: app widget, wallpaper app, TV app, watch app, car app, etc.</li></ul><p>If you feel really adventurous, you could:</p><ul><li>Create a new protocol that uses ActivityPub. In other words, &quot;my project is X, but using ActivityPub&quot;, where you come up with your own value for X. That could be tied to software development (&quot;my project is F-Droid, but using ActivityPub&quot;, &quot;my project is Stack Overflow, but using ActivityPub&quot;) or it could be broader in scope (&quot;my project is Reddit, but using ActivityPub&quot;). This likely will require some server-side development, in addition to client-side libraries and apps. On the other hand, &quot;the sky&apos;s the limit&quot;.</li><li>You could leverage that &quot;generic consumption of feeds&quot;-style library to create <a href="https://furbo.org/2023/01/15/the-shit-show/">the truly universal timeline app</a>.</li><li>You could experiment with ActivityPub for situations where there may not be a stable Internet connection, or where Internet connections might be monitored continuously. Can you come up with an Android ActivityPub bridge that uses Bluetooth, WiFi Direct, or other communications protocol, to help conventional ActivityPub clients deal with inconsistent or inadvisable network access?</li><li>You could aim to become a freelance developer advocate for ActivityPub. Perhaps your hope will be that if many ActivityPub apps become popular enough, ActivityPub will become a major piece of foundation technology. Crazier things have happened.</li></ul><p>I think that <a href="https://www.techdirt.com/2022/12/30/new-years-message-the-opportunity-to-build-a-better-internet-is-here-right-now/">the time is ripe for some good old-fashioned business model disruption</a>. I think that ActivityPub is in good position to help with that disruption. And I think that there will be many opportunities for Android developers with demonstrated ActivityPub expertise, where that expertise can &quot;open doors&quot; that might otherwise not open for you. And, the nice thing is that even if ActivityPub does not become huge, getting involved with ActivityPub can cost you nothing more than time.</p><p>I look forward to what we build!</p><p>(and if somehow your project is &quot;overcoming hair loss, but using ActivityPub&quot;... <a href="mailto:hey.balding.guy@commonsware.com">ping me</a>)</p>]]></content:encoded></item><item><title><![CDATA[Hello Android Dev Social]]></title><description><![CDATA[How I learned to stop worrying and love the fediverse ]]></description><link>https://androiddev.blog/hello-and-welcome-to-android-dev-social/</link><guid isPermaLink="false">63b2141ea6401500017a3f54</guid><dc:creator><![CDATA[Mike Nakhimovich]]></dc:creator><pubDate>Fri, 30 Dec 2022 18:08:35 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://androiddev.blog/content/images/2023/01/image.png" class="kg-image" alt loading="lazy" width="2000" height="1050" srcset="https://androiddev.blog/content/images/size/w600/2023/01/image.png 600w, https://androiddev.blog/content/images/size/w1000/2023/01/image.png 1000w, https://androiddev.blog/content/images/size/w1600/2023/01/image.png 1600w, https://androiddev.blog/content/images/2023/01/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Android Dev Social Logo</figcaption></figure><h2 id="what-a-wild-ride"><strong>What a wild ride</strong></h2><p><em>Two</em> months ago I was employed at Twitter which looked more and more like it was going to fall off a cliff. I had zero experience managing or deploying applications. Today, I am 6 weeks into my resignation and created a community of over 2000 Android Developers and a newly formed blog. I wanted to take some time and talk about how I got here and what makes <a href="http://androiddev.social/">androiddev.social</a> what it is.</p><h2 id="about-me"><strong>About Me</strong></h2><p>I&apos;ve been a Twitter user &#xA0;and an Android Engineer for over a decade. Before being a casual user, I was using Twitter as a poor man&apos;s pager duty at a small company. Over time Twitter grew to be my most visited website and eventually my employer. Over the past decade I was able to amass 7000+ followers making me more popular on Twitter than I have ever been anywhere. It truly felt like a place I felt comfortable to interact with folks. I used it to announce new libraries/blog posts, get support and generally talk to the Android community at large.</p><h2 id="the-tipping-point"><strong>The Tipping Point</strong></h2><p>On Nov 4th, 2022, after an all-hands at work, I knew I could no longer be employed or a user of Twitter, I wanted to not only get off the platform but find a new home for others like me. Over the last few months I saw more and more people post their Mastodon handles on Twitter. I didn&apos;t have an account, and I thought it would be the right place to try.</p><h2 id="finding-a-new-home"><strong>Finding a New Home</strong></h2><p>At the time, most of the big servers were private so I looked for one that &#xA0;I could pay for. &#xA0;This led me to <a href="http://masto.host/">masto.host</a> who was luckily still open for new sign ups. Rather than giving you an account on someone&apos;s server, <a href="http://masto.host/">masto.host</a> let you pay for your own mastodon instance. I registered <a href="http://androiddev.social/">androiddev.social </a>with <a href="http://domains.google.com/">Google domains</a> and off I went. My goal was to advertise it a bit on some communities I am a part of and see if a few other folks wanted to join. We grew organically from the start.</p><h2 id="inviting-friends"><strong>Inviting Friends</strong></h2><p>Within 24 hours there was a waitlist of over 1000 android engineers. It was time to scale. I contacted our lovely host (who had closed public signups) and asked if they were willing to move us to a bigger VM. &#xA0;They obliged and we were now ready for roughly 2000 members at a cost of $110 a month.</p><h2 id="legal-protection"><strong>Legal Protection</strong></h2><p>Being paranoid I knew it was time to form an LLC, I&apos;ve used <a href="http://incfile.com/">incfile.com</a> in the past and went with it again. It took a few days and about $400 for Android Dev Social LLC to be registered in NJ. &#xA0;With a registered LLC, I could open a bank account.</p><h2 id="funding"><strong>Funding</strong></h2><p>While I was willing to pay a few bucks a month, it was clear that this would start costing more than I can carry on my own, so I solicited donations. My first try at payment processing was through Patreon. &#xA0;The interface was easy and I was accepting payments within an hour of signing up. &#xA0;Easy comes with a cost and it was as high as 20% of a donation. It didn&apos;t feel right to have someone&apos;s donation go to Patreon.</p><p>My next try was with Stripe. Stripe charged 2.9% + 30 cents per transaction. It was great for anything that was $5+ but was still not ideal for $1 donations (took 35 cents!). &#xA0;I am still with Stripe because itworks globally and seems to be the cheapest I can find.</p><h2 id="asking-for-help"><strong>Asking for help</strong></h2><p>I welcomed our first mods as well. We now have a team of 7 moderators reviewing sign ups, banning content and building things like this blog.</p><h2 id="two-weeks-in"><strong>Two Weeks In</strong></h2><p>By November 14th, <a href="http://androiddev.social/">androiddev.social</a> had 1,600 users, 7 moderators, an LLC and a bank account. What started as a distraction from the Twitter tire fire was blossoming into an ever growing community.</p><h2 id="outpouring-of-support"><strong>Outpouring of support</strong></h2><p>Over the last two months I have received 200+ donations ranging from $1/mo from over 100 members to large one time donations from Droidcon and Android Developers (Google). Overall we have collected over $7,000 allowing us to prepay for a year of hosting and expand to things like this blog.</p><h2 id="introducing-blogandroiddevsocial"><strong>Introducing </strong><a href="https://androiddev.blog/"><strong>blog.androiddev.social</strong></a></h2><p>Besides a Twitter replacement, there was one more online tool I was hoping to create for the Android community and that was a Medium replacement. Many Android engineers previously published on Medium. In recent years, policies there have made it increasingly difficult to use for free. Since we have extra funding, I spent the last few days getting a ghost blog hosted on Digital Ocean. &#xA0;This blog post along with many more to follow are being hosted on a $12 a month Digital Ocean droplet. &#xA0;We&apos;d love to grow this as a public publication so if you are interested in contributing please reach out to <a href="http://admin@androiddev.social">admin@androiddev.social</a>.</p><h2 id="moving-payments"><strong>Moving Payments</strong></h2><p>Ghost is much fancier than what I remember it being from a few years ago. Ghost 5 is a full featured CMS rather than a blogging platform. It also has Stripe integration and member management. As of yesterday, we have migrated our donation page to be within Ghost <a href="https://androiddev.blog/#/portal/">https://blog.androiddev.social/#/portal/</a>. The biggest addition here is ability to let folks increase/decrease/cancel their donation levels as well as automation around sending invoices and receipts. Think of it as all the nice things we had with Patreon but being able to get it for free, as part of our Stripe fees.</p><h2 id="plans-for-2023"><strong>Plans for 2023</strong></h2><p>Over the next year I expect to double the number of folks that use <a href="http://androiddev.social/">androiddev.social</a>. I also hope to expand this blog to be a newsletter and general center for Android communication online. We are working on merchandising as well. Donations will be eligible for stickers or t-shirts.</p><p>On the business side I need to find an accountant and figure out taxes. I&#x2019;m also looking for a few larger sponsors to help with efforts like bounties for app development. Please contact us at <a href="http://admin@androiddev.social">admin@androiddev.social</a> if you&apos;d like to support us at the corporate level.</p><p>Big thank you to our mods and everyone who has helped along the way.</p><p>Donations are appreciated and can be made through: &#xA0; <a href="https://androiddev.blog/#/portal/">https://blog.androiddev.social/#/portal/</a><br></p><p></p><p></p><p></p><p> </p>]]></content:encoded></item><item><title><![CDATA[Coming Soon to Android Dev Social]]></title><description><![CDATA[<p>This is our brand new donation page. Eventually we hope to turn it into a blog. &#xA0;Things will be up and running shortly, but you can <a href="#/portal/">donate</a> in the meantime if you want to show your support. Thank you. </p><p>Current subscribers through stripe - feel free to unsubscribe <a href="https://billing.stripe.com/p/login/fZebIQ5dX2XMfU49AA">here</a></p>]]></description><link>https://androiddev.blog/coming-soon-2/</link><guid isPermaLink="false">63b2141ea6401500017a3f52</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Admin]]></dc:creator><pubDate>Thu, 29 Dec 2022 16:07:41 GMT</pubDate><content:encoded><![CDATA[<p>This is our brand new donation page. Eventually we hope to turn it into a blog. &#xA0;Things will be up and running shortly, but you can <a href="#/portal/">donate</a> in the meantime if you want to show your support. Thank you. </p><p>Current subscribers through stripe - feel free to unsubscribe <a href="https://billing.stripe.com/p/login/fZebIQ5dX2XMfU49AA">here</a> and then subscribing above. The advantage will be access to a portal we built on this blog. The portal allows you to update or cancel your subscription.</p>]]></content:encoded></item></channel></rss>